<commit_before>package chip8

import (
	"fmt"
	"log"
	"math/rand"
	"time"
)

type memory []byte
type instruction uint16
type address uint16

type CPU struct {
	PC          address `PC`
	I           address `I`
	V           memory  `V`
	draw        bool    `Draw`
	sound_timer byte    `sound_timer`
	delay_timer byte    `delay_timer`
	sp          byte    `sp`
	stack       []address
	key         memory
	mem         memory
	gfx         memory
}

var BEGIN_LOAD = address(0x200)

var FONT memory = memory([]byte{
	0xF0, 0x90, 0x90, 0x90, 0xF0, // 0
	0x20, 0x60, 0x20, 0x20, 0x70, // 1
	0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2
	0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3
	0x90, 0x90, 0xF0, 0x10, 0x10, // 4
	0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5
	0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6
	0xF0, 0x10, 0x20, 0x40, 0x40, // 7
	0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8
	0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9
	0xF0, 0x90, 0xF0, 0x90, 0x90, // A
	0xE0, 0x90, 0xE0, 0x90, 0xE0, // B
	0xF0, 0x80, 0x80, 0x80, 0xF0, // C
	0xE0, 0x90, 0x90, 0x90, 0xE0, // D
	0xF0, 0x80, 0xF0, 0x80, 0xF0, // E
	0xF0, 0x80, 0xF0, 0x80, 0x80, // F
})

func New(rom memory) *CPU {
	c := &CPU{
		PC:          BEGIN_LOAD,
		I:           0,
		draw:        false,
		sound_timer: 0,
		delay_timer: 0,
		stack:       make([]address, 16),
		V:           make(memory, 16),
		mem:         make(memory, 4096),
		gfx:         make(memory, 64*32),
		key:         make(memory, 16),
	}
	copy(c.mem[c.PC:], rom)
	copy(c.mem[0x50:], FONT)
	rand.Seed(time.Now().UnixNano())

	return c
}

func (c *CPU) Step() error {
	opCode := decodeOpCode(c.PC, c.mem)

	instruction := (opCode & 0xF000) >> 12
	if instruction == 0x0 {
		//TODO Refactor
		if opCode == 0x00E0 {
			c.PC = c.PC + 2

			log.Println("%x CLS", c.PC)
		} else if opCode == 0x00EE {
			c.returnFromSubroutine()
		} else {
			c.PC = c.PC + 2

			log.Printf("%x SYS %x", c.PC, opCode&0xFF0F)
		}
	} else if instruction == 0x1 {
		//Jump to address nnn
		c.jumpToAddress(opCode)
	} else if instruction == 0x2 {
		//Execute subroutine starting at address NNN
		c.callSubroutine(opCode)
	} else if instruction == 0x3 {
		//SE Vx, byte
		//TODO Refactor
		registry := int(opCode & 0x0F00 >> 8)
		value := byte(opCode & 0x00FF)
		if c.V[registry] == value {
			c.PC = c.PC + 2
		}
		c.PC = c.PC + 2
		log.Printf("%x SE V%d, %.2x", c.PC, registry, value)
	} else if instruction == 0x4 {
		//SNE Vx, byte
		//TODO Refactor
		registry := int(opCode & 0x0F00 >> 8)
		value := byte(opCode & 0x00FF)
		if c.V[registry] != value {
			c.PC = c.PC + 2
		}
		c.PC = c.PC + 2
		log.Printf("%x SNE V%d, %.2x", c.PC, registry, value)
	} else if instruction == 0x6 {
		//LD Vx, byte
		c.loadToRegister(opCode)
	} else if instruction == 0x7 {
		//ADD Vx, byte
		//TODO Refactor
		x := uint8(opCode & 0x0F00 >> 8)
		value := byte(opCode & 0x00FF)
		c.V[x] = c.V[x] + value
		c.PC = c.PC + 2
		log.Printf("%x ADD V%d, %.2x", c.PC, x, value)
	} else if instruction == 0x8 {
		//TODO Refactor
		if opCode&0xF00F == 0x8000 {
			x := opCode & 0x0F00 >> 8
			y := opCode & 0x00F0 >> 4
			log.Printf("%x LD V%d, V%d", c.PC, x, y)
		} else if opCode&0xF00F == 0x8001 {
			//TODO
		} else if opCode&0xF00F == 0x8002 {
			//TODO
		} else if opCode&0xF00F == 0x8003 {
			//TODO
		} else if opCode&0xF00F == 0x8004 {
			c.addRegisters(opCode)
		} else if opCode&0xF00F == 0x8005 {
			//TODO
		} else if opCode&0xF00F == 0x8006 {
			//TODO
		} else if opCode&0xF00F == 0x8007 {
			//TODO
		} else if opCode&0xF00F == 0x800E {
			//TODO
		} else {
			log.Fatalf("Unknown 0x800 instruction %x", opCode)
		}
		c.PC = c.PC + 2
	} else if instruction == 0xA {
		//LD I, addr
		c.loadToI(opCode)
	} else if instruction == 0xC {
		//Sets VX to a random number and NN
		//TODO Refactor
		x := uint8(opCode & 0x0F00)
		random := uint8(rand.Intn(255))
		c.V[x] = random & uint8(opCode&0x00FF)
		log.Printf("%x RND V%d, %d", c.PC, x, c.V[x])
		c.PC = c.PC + 2
	} else if instruction == 0xD {
		//DRW Vx, Vy, nibble
		//TODO Refactor
		x := opCode & 0x0F00 >> 8
		y := opCode & 0x00F0 >> 4
		n := opCode & 0x000F
		c.PC = c.PC + 2
		log.Printf("%x DRW V%d, V%d, %d", c.PC, x, y, n)
	} else if instruction == 0xF {
		//TODO Refactor
		registry := uint8(opCode & 0x0F00 >> 8)
		operation := opCode & 0xF0FF
		if operation == 0xF007 {
			log.Printf("%x Set V[%d] = delay timer value", c.PC, registry)
			c.V[registry] = c.delay_timer
		} else if operation == 0xF015 {
			log.Printf("%x Set delay timer value = V[%d]", c.PC, registry)
			c.delay_timer = c.V[registry]
		} else {
			log.Printf("%x F %x %x", c.PC, opCode, operation)
		}
		c.PC = c.PC + 2
	} else {
		log.Fatalf("%x Unknown %x %x", c.PC, opCode, opCode>>12)
	}

	if c.sound_timer > 0 {
		if c.sound_timer == 1 {
			fmt.Println("BEEP")
		}
		c.sound_timer--
	}

	if c.delay_timer > 0 {
		c.delay_timer--
	}
	return nil
}

func decodeOpCode(memAddress address, mem memory) instruction {
	opCode := instruction(mem[uint16(memAddress)])<<8 | instruction(mem[uint16(memAddress+1)])
	return instruction(opCode)
}

func (c *CPU) callSubroutine(opCode instruction) {
	//Call subroutine at nnn
	addr := address(opCode & 0x0FFF)
	c.stack[c.sp] = c.PC
	c.sp++
	c.PC = addr

	log.Printf("%x Call subroutine at %.3x %x", c.PC, addr, c.PC)
}

func (c *CPU) loadToRegister(opCode instruction) {
	//LD Vx, byte
	registerId := uint8(opCode & 0x0F00 >> 8)
	byte := byte(opCode & 0x00FF)
	c.V[registerId] = byte
	c.PC = c.PC + 2

	log.Printf("%x LD V%d, %.2x", c.PC, registerId, byte)
}

func (c *CPU) loadToI(opCode instruction) {
	addr := address(opCode & 0x0FFF)
	c.I = addr
	c.PC = c.PC + 2

	log.Printf("%x LD I, %.3x", c.PC, addr)
}

func (c *CPU) jumpToAddress(opCode instruction) {
	c.PC = address(opCode & 0x0FFF)
	log.Printf("%x JP %.3x", c.PC, c.PC)
}

func (c *CPU) returnFromSubroutine() {
	c.sp--
	c.PC = c.stack[c.sp]

	log.Printf("%x RET %.3x %d", c.PC, c.PC, c.sp)
}

func (c *CPU) addRegisters(opCode instruction) {
	//ADD Vx, Vy
	x := uint8(opCode & 0x0F00 >> 8)
	y := uint8(opCode & 0x00F0 >> 4)
	z := uint16(c.V[x]) + uint16(c.V[y])
	if z > 255 {
		c.V[0xf] = 1
	}
	c.V[x] = byte(z & 0x0f)
	log.Printf("%x ADD V%d, V%d", c.PC, x, y)
}
<commit_msg>Fixed logging<commit_after>package chip8

import (
	"fmt"
	"log"
	"math/rand"
	"time"
)

type memory []byte
type instruction uint16
type address uint16

type CPU struct {
	PC          address `PC`
	I           address `I`
	V           memory  `V`
	draw        bool    `Draw`
	sound_timer byte    `sound_timer`
	delay_timer byte    `delay_timer`
	sp          byte    `sp`
	stack       []address
	key         memory
	mem         memory
	gfx         memory
}

var BEGIN_LOAD = address(0x200)

var FONT memory = memory([]byte{
	0xF0, 0x90, 0x90, 0x90, 0xF0, // 0
	0x20, 0x60, 0x20, 0x20, 0x70, // 1
	0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2
	0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3
	0x90, 0x90, 0xF0, 0x10, 0x10, // 4
	0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5
	0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6
	0xF0, 0x10, 0x20, 0x40, 0x40, // 7
	0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8
	0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9
	0xF0, 0x90, 0xF0, 0x90, 0x90, // A
	0xE0, 0x90, 0xE0, 0x90, 0xE0, // B
	0xF0, 0x80, 0x80, 0x80, 0xF0, // C
	0xE0, 0x90, 0x90, 0x90, 0xE0, // D
	0xF0, 0x80, 0xF0, 0x80, 0xF0, // E
	0xF0, 0x80, 0xF0, 0x80, 0x80, // F
})

func New(rom memory) *CPU {
	c := &CPU{
		PC:          BEGIN_LOAD,
		I:           0,
		draw:        false,
		sound_timer: 0,
		delay_timer: 0,
		stack:       make([]address, 16),
		V:           make(memory, 16),
		mem:         make(memory, 4096),
		gfx:         make(memory, 64*32),
		key:         make(memory, 16),
	}
	copy(c.mem[c.PC:], rom)
	copy(c.mem[0x50:], FONT)
	rand.Seed(time.Now().UnixNano())

	return c
}

func (c *CPU) Step() error {
	opCode := decodeOpCode(c.PC, c.mem)

	instruction := (opCode & 0xF000) >> 12
	if instruction == 0x0 {
		//TODO Refactor
		if opCode == 0x00E0 {
			log.Println("%x CLS", c.PC)
			c.PC = c.PC + 2
		} else if opCode == 0x00EE {
			c.returnFromSubroutine()
		} else {
			log.Printf("%x SYS %x", c.PC, opCode&0xFF0F)
			c.PC = c.PC + 2
		}
	} else if instruction == 0x1 {
		//Jump to address nnn
		c.jumpToAddress(opCode)
	} else if instruction == 0x2 {
		//Execute subroutine starting at address NNN
		c.callSubroutine(opCode)
	} else if instruction == 0x3 {
		//SE Vx, byte
		//TODO Refactor
		registry := int(opCode & 0x0F00 >> 8)
		value := byte(opCode & 0x00FF)
		if c.V[registry] == value {
			c.PC = c.PC + 2
		}
		log.Printf("%x SE V%d, %.2x", c.PC, registry, value)
		c.PC = c.PC + 2
	} else if instruction == 0x4 {
		//SNE Vx, byte
		//TODO Refactor
		registry := int(opCode & 0x0F00 >> 8)
		value := byte(opCode & 0x00FF)
		if c.V[registry] != value {
			c.PC = c.PC + 2
		}
		log.Printf("%x SNE V%d, %.2x", c.PC, registry, value)
		c.PC = c.PC + 2
	} else if instruction == 0x6 {
		//LD Vx, byte
		c.loadToRegister(opCode)
	} else if instruction == 0x7 {
		//ADD Vx, byte
		//TODO Refactor
		x := uint8(opCode & 0x0F00 >> 8)
		value := byte(opCode & 0x00FF)
		c.V[x] = c.V[x] + value
		log.Printf("%x ADD V%d, %.2x", c.PC, x, value)
		c.PC = c.PC + 2
	} else if instruction == 0x8 {
		//TODO Refactor
		if opCode&0xF00F == 0x8000 {
			x := opCode & 0x0F00 >> 8
			y := opCode & 0x00F0 >> 4
			log.Printf("%x LD V%d, V%d", c.PC, x, y)
		} else if opCode&0xF00F == 0x8001 {
			//TODO
		} else if opCode&0xF00F == 0x8002 {
			//TODO
		} else if opCode&0xF00F == 0x8003 {
			//TODO
		} else if opCode&0xF00F == 0x8004 {
			c.addRegisters(opCode)
		} else if opCode&0xF00F == 0x8005 {
			//TODO
		} else if opCode&0xF00F == 0x8006 {
			//TODO
		} else if opCode&0xF00F == 0x8007 {
			//TODO
		} else if opCode&0xF00F == 0x800E {
			//TODO
		} else {
			log.Fatalf("Unknown 0x800 instruction %x", opCode)
		}
		c.PC = c.PC + 2
	} else if instruction == 0xA {
		//LD I, addr
		c.loadToI(opCode)
	} else if instruction == 0xC {
		//Sets VX to a random number and NN
		//TODO Refactor
		x := uint8(opCode & 0x0F00)
		random := uint8(rand.Intn(255))
		c.V[x] = random & uint8(opCode&0x00FF)
		log.Printf("%x RND V%d, %d", c.PC, x, c.V[x])
		c.PC = c.PC + 2
	} else if instruction == 0xD {
		//DRW Vx, Vy, nibble
		//TODO Refactor
		x := opCode & 0x0F00 >> 8
		y := opCode & 0x00F0 >> 4
		n := opCode & 0x000F
		log.Printf("%x DRW V%d, V%d, %d", c.PC, x, y, n)
		c.PC = c.PC + 2
	} else if instruction == 0xF {
		//TODO Refactor
		registry := uint8(opCode & 0x0F00 >> 8)
		operation := opCode & 0xF0FF
		if operation == 0xF007 {
			log.Printf("%x Set V[%d] = delay timer value", c.PC, registry)
			c.V[registry] = c.delay_timer
		} else if operation == 0xF015 {
			log.Printf("%x Set delay timer value = V[%d]", c.PC, registry)
			c.delay_timer = c.V[registry]
		} else {
			log.Printf("%x F %x %x", c.PC, opCode, operation)
		}
		c.PC = c.PC + 2
	} else {
		log.Fatalf("%x Unknown %x %x", c.PC, opCode, opCode>>12)
	}

	if c.sound_timer > 0 {
		if c.sound_timer == 1 {
			fmt.Println("BEEP")
		}
		c.sound_timer--
	}

	if c.delay_timer > 0 {
		c.delay_timer--
	}
	return nil
}

func decodeOpCode(memAddress address, mem memory) instruction {
	opCode := instruction(mem[uint16(memAddress)])<<8 | instruction(mem[uint16(memAddress+1)])
	return instruction(opCode)
}

func (c *CPU) callSubroutine(opCode instruction) {
	//Call subroutine at nnn
	oldPC := c.PC
	addr := address(opCode & 0x0FFF)
	c.stack[c.sp] = c.PC
	c.sp++
	c.PC = addr

	log.Printf("%x Call subroutine at %.3x %x", oldPC, addr, c.PC)
}

func (c *CPU) loadToRegister(opCode instruction) {
	//LD Vx, byte
	registerId := uint8(opCode & 0x0F00 >> 8)
	byte := byte(opCode & 0x00FF)
	c.V[registerId] = byte

	log.Printf("%x LD V%d, %.2x", c.PC, registerId, byte)
	c.PC = c.PC + 2
}

func (c *CPU) loadToI(opCode instruction) {
	addr := address(opCode & 0x0FFF)
	c.I = addr

	log.Printf("%x LD I, %.3x", c.PC, addr)
	c.PC = c.PC + 2
}

func (c *CPU) jumpToAddress(opCode instruction) {
	oldPC := c.PC
	c.PC = address(opCode & 0x0FFF)
	log.Printf("%x JP %.3x", oldPC, c.PC)
}

func (c *CPU) returnFromSubroutine() {
	oldPC := c.PC
	c.sp--
	c.PC = c.stack[c.sp]

	log.Printf("%x RET %.3x %d", oldPC, c.PC, c.sp)
}

func (c *CPU) addRegisters(opCode instruction) {
	//ADD Vx, Vy
	x := uint8(opCode & 0x0F00 >> 8)
	y := uint8(opCode & 0x00F0 >> 4)
	z := uint16(c.V[x]) + uint16(c.V[y])
	if z > 255 {
		c.V[0xf] = 1
	}
	c.V[x] = byte(z & 0x0f)
	log.Printf("%x ADD V%d, V%d", c.PC, x, y)
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"fmt"
	"github.com/codegangsta/cli"
	"github.com/inhies/go-bytesize" // Do we really need this?
	"io/ioutil"
	"net/url"
	"os"
	"os/exec"
	upath "path"
	fpath "path/filepath"
	"regexp"
	"strconv"
	"strings"
	"text/template"
)

type WikiTrackData struct {
	Duration                 string
	FolderName               string
	Index                    int
	HasAlternateLeadVocalist bool
	Name                     string
	CD                       int
	LinePrefix               string
}

type WikiAlbumData struct {
	Notes      string
	FolderName string
	Tracks     []WikiTrackData
	Duration   string
	Lineage    string
	Size       string
	SampleRate string
	BPS        string
}

func generateWikifiles(c *cli.Context) {
	fileInfo, filepath := checkFilepathArgument(c)
	if fileInfo == nil {
		return
	}

	mode := "batch"
	if c.GlobalBool("single") {
		mode = "single"
	}

	fmt.Printf("The following filepath (%s mode) will be processed: %s\n", mode, filepath)
	notifyDeleteMode(c)

	if !shouldContinue(c) {
		return
	}

	regex, err := regexp.Compile(wikiRegex)
	if err != nil {
		fmt.Println("Internal error - wiki regex could not be compiled!")
		fmt.Println(err.Error())
		os.Exit(1)
	}

	wikiTemplate, err := template.New("wiki").Parse(
		// Stupid windows
		strings.Replace(wikiTemplate, "\n", "\r\n", -1),
	)
	if err != nil {
		fmt.Println("Internal error - wiki template could not be parsed!")
		fmt.Println(err.Error())
		os.Exit(1)
	}

	if mode == "single" {
		generateWikifile(filepath, fileInfo.Name(), regex, wikiTemplate, c.GlobalBool("delete"), "")
		return
	}

	// Create the wikifiles folder path
	wikifiles := fpath.Join(filepath, "__wikifiles")

	// MkdirAll is used instead of Mkdir because this function
	// doesn't error if the folder already exists
	err = os.MkdirAll(wikifiles, os.ModeDir)
	if err != nil {
		fmt.Println("Internal error creating __wikifiles folder")
		fmt.Println(err.Error())
		os.Exit(1)
	}

	files, _ := ioutil.ReadDir(filepath)
	for _, file := range files {
		if file.IsDir() {
			name := file.Name()
			if name != "__wikifiles" {
				generateWikifile(fpath.Join(filepath, name), name, regex, wikiTemplate, c.GlobalBool("delete"), wikifiles)
			}
		}
	}
}

func wikiGetInfoFromFlac(filepath string, parsedData *WikiAlbumData) bool {
	directoryContents, err := ioutil.ReadDir(filepath)
	if err != nil {
		fmt.Println("failed to get directory contents")
		fmt.Println(err)
		return false
	}

	for _, file := range directoryContents {
		if file.IsDir() {
			if wikiGetInfoFromFlac(fpath.Join(filepath, file.Name()), parsedData) {
				return true
			}
		} else if fpath.Ext(file.Name()) == ".flac" {
			data, err := exec.Command(
				"metaflac",
				"--show-sample-rate",
				"--show-bps", // update numbers below
				fpath.Join(filepath, file.Name()),
			).Output()

			if err != nil {
				fmt.Println("metaflac returned an invalid response for sample-rate/bps")
				if data != nil {
					fmt.Println(data)
				}
				panic(err)
			}

			lines := strings.Split(string(data), "\n")
			if len(lines) != 1+2 {
				// Update '2' below and above
				fmt.Println("expected 2 metaflac lines, got", len(lines)-1)
				continue
			}

			failure := false
			for i, line := range lines {
				line := strings.TrimSpace(line)

				if i == 0 {
					rateFloat, err := strconv.ParseFloat(line, 32)
					if err != nil {
						fmt.Print("could not convert sample-rate to float")
						fmt.Println(err)
						failure = true
						break
					}
					parsedData.SampleRate = strconv.FormatFloat(rateFloat/1000, 'f', -1, 32) + "KHz"
				} else if i == 1 {
					parsedData.BPS = line
				}
			}

			if !failure {
				return true
			}
		}
	}

	fmt.Println("failed to find file for sampling info")
	return false
}

func generateWikifile(filepath string, foldername string, regex *regexp.Regexp, wikiTemplate *template.Template, deleteMode bool, outBasepath string) {
	basepath := fpath.Join(filepath, foldername)
	infofile := basepath + ".txt"
	wikifile := filepath
	if outBasepath != "" {
		wikifile = outBasepath
	}

	if deleteMode {
		fmt.Printf("Deleting wiki from %s...", infofile)
	} else {
		fmt.Printf("Generating from %s... ", infofile)
	}

	infobytes, err := ioutil.ReadFile(infofile)
	if err != nil {
		if os.IsNotExist(err) {
			fmt.Println("infofile doesn't exist")
		} else {
			fmt.Printf("error (%s)\n", err.Error())
		}
		return
	}

	matches := regex.FindSubmatch(infobytes)
	if len(matches) != 1+regex.NumSubexp() {
		// (entire string itself)+(capture groups)
		fmt.Printf("parse failure, expected %d capturing groups!\n", 1+regex.NumSubexp())
		return
	}

	var parsedData WikiAlbumData
	parsedData.FolderName = foldername

	size, err := getDirectorySize(filepath)
	if err != nil {
		fmt.Println("failed to get directory size")
		fmt.Println(err)
		return
	}
	b := bytesize.New(size)
	parsedData.Size = b.String()

	if !wikiGetInfoFromFlac(filepath, &parsedData) {
		return
	}

	tracks := make([]WikiTrackData, 0)

	var lastTrack WikiTrackData
	var currentTrackNumber int = 0

	for i, field := range matches {
		if i == 0 {
			// The first one is just itself
			continue
		}

		field := string(bytes.TrimSpace(field))

		switch i {
		case 1:
			if field != "" {
				parsedData.Lineage = "*" + field + "\n"
			}
		case 2:
			lineage := ""
			for _, item := range strings.Split(field, "\n") {
				lineage += "*" + strings.TrimSpace(item) + "\r\n"
			}
			parsedData.Lineage += lineage
		case 3:
			parsedData.Notes = field
		case 4:
			str, err := url.QueryUnescape(field)
			if err != nil {
				fmt.Println("error unescaping query from url")
				fmt.Println(err.Error())
				return
			}
			wikifile = fpath.Join(wikifile, strings.Replace(strings.Replace(str, "_", " ", -1), "/", "_", -1))
			fmt.Printf("\n - %s... ", wikifile)
		case 5:
			// parse tracks
			for _, track := range strings.Split(field, "\n") {
				var trackData WikiTrackData
				trackData.FolderName = foldername
				trackData.LinePrefix = "#"

				str := strings.TrimSpace(track)
				f := strings.Index(str, "[")
				l := strings.Index(str, "]")
				trackData.Duration = str[f+1 : l]

				number := str[:f-2]
				separator := strings.Index(number, ".")
				if separator != -1 {
					// This bit only uses the "path" library
					// because URL's only use forward slash
					cdStr := number[:separator]

					cdNumber, err := strconv.Atoi(cdStr)
					if err != nil {
						panic(err.Error())
					}
					trackData.FolderName = upath.Join(foldername, "CD"+cdStr)
					trackData.CD = cdNumber

					if lastTrack.CD != cdNumber {
						currentTrackNumber = 0
						trackData.LinePrefix = fmt.Sprintf("\r\nCD%d:\r\n%s", cdNumber, trackData.LinePrefix)
					}
				} else {
					// Here lies incomplete support for "Bonus." tracks
					// you need to add support for using filenames instead
					// of tracknumbers for bonus tracks

					// _, err := strconv.Atoi(number)
					// if err != nil {
					// 	trackData.FolderName = number
					// 	trackData.CD = -1

					// 	if lastTrack.FolderName != number {
					// 		currentTrackNumber = 0
					// 	}
					// }
					// continue
				}

				name := strings.TrimSpace(str[l+1:])
				nameWithoutSuffix := strings.TrimSuffix(name, " (*)")
				trackData.HasAlternateLeadVocalist = name != nameWithoutSuffix
				trackData.Name = nameWithoutSuffix

				currentTrackNumber += 1
				trackData.Index = currentTrackNumber

				lastTrack = trackData
				tracks = append(tracks, trackData)
			}
		case 6:
			parsedData.Duration = field
		}
	}
	parsedData.Tracks = tracks

	success := removeFile(wikifile, false)
	if deleteMode {
		message := "success!"
		if !success {
			message = "couldn't delete!"
		}
		fmt.Println(message)
		return
	} else if success {
		fmt.Print("overwritten... ")
	}

	wikiout := createFile(wikifile)
	defer wikiout.Close()

	if wikiout != nil {
		err = wikiTemplate.Execute(wikiout, parsedData)
		if err != nil {
			fmt.Println("could not insert data into template!")
			fmt.Println(err)
			return
		}

		fmt.Println("success!")
	}
}
<commit_msg>Fix missing wikifile ext's<commit_after>package main

import (
	"bytes"
	"fmt"
	"github.com/codegangsta/cli"
	"github.com/inhies/go-bytesize" // Do we really need this?
	"io/ioutil"
	"net/url"
	"os"
	"os/exec"
	upath "path"
	fpath "path/filepath"
	"regexp"
	"strconv"
	"strings"
	"text/template"
)

type WikiTrackData struct {
	Duration                 string
	FolderName               string
	Index                    int
	HasAlternateLeadVocalist bool
	Name                     string
	CD                       int
	LinePrefix               string
}

type WikiAlbumData struct {
	Notes      string
	FolderName string
	Tracks     []WikiTrackData
	Duration   string
	Lineage    string
	Size       string
	SampleRate string
	BPS        string
}

func generateWikifiles(c *cli.Context) {
	fileInfo, filepath := checkFilepathArgument(c)
	if fileInfo == nil {
		return
	}

	mode := "batch"
	if c.GlobalBool("single") {
		mode = "single"
	}

	fmt.Printf("The following filepath (%s mode) will be processed: %s\n", mode, filepath)
	notifyDeleteMode(c)

	if !shouldContinue(c) {
		return
	}

	regex, err := regexp.Compile(wikiRegex)
	if err != nil {
		fmt.Println("Internal error - wiki regex could not be compiled!")
		fmt.Println(err.Error())
		os.Exit(1)
	}

	wikiTemplate, err := template.New("wiki").Parse(
		// Stupid windows
		strings.Replace(wikiTemplate, "\n", "\r\n", -1),
	)
	if err != nil {
		fmt.Println("Internal error - wiki template could not be parsed!")
		fmt.Println(err.Error())
		os.Exit(1)
	}

	if mode == "single" {
		generateWikifile(filepath, fileInfo.Name(), regex, wikiTemplate, c.GlobalBool("delete"), "")
		return
	}

	// Create the wikifiles folder path
	wikifiles := fpath.Join(filepath, "__wikifiles")

	// MkdirAll is used instead of Mkdir because this function
	// doesn't error if the folder already exists
	err = os.MkdirAll(wikifiles, os.ModeDir)
	if err != nil {
		fmt.Println("Internal error creating __wikifiles folder")
		fmt.Println(err.Error())
		os.Exit(1)
	}

	files, _ := ioutil.ReadDir(filepath)
	for _, file := range files {
		if file.IsDir() {
			name := file.Name()
			if name != "__wikifiles" {
				generateWikifile(fpath.Join(filepath, name), name, regex, wikiTemplate, c.GlobalBool("delete"), wikifiles)
			}
		}
	}
}

func wikiGetInfoFromFlac(filepath string, parsedData *WikiAlbumData) bool {
	directoryContents, err := ioutil.ReadDir(filepath)
	if err != nil {
		fmt.Println("failed to get directory contents")
		fmt.Println(err)
		return false
	}

	for _, file := range directoryContents {
		if file.IsDir() {
			if wikiGetInfoFromFlac(fpath.Join(filepath, file.Name()), parsedData) {
				return true
			}
		} else if fpath.Ext(file.Name()) == ".flac" {
			data, err := exec.Command(
				"metaflac",
				"--show-sample-rate",
				"--show-bps", // update numbers below
				fpath.Join(filepath, file.Name()),
			).Output()

			if err != nil {
				fmt.Println("metaflac returned an invalid response for sample-rate/bps")
				if data != nil {
					fmt.Println(data)
				}
				panic(err)
			}

			lines := strings.Split(string(data), "\n")
			if len(lines) != 1+2 {
				// Update '2' below and above
				fmt.Println("expected 2 metaflac lines, got", len(lines)-1)
				continue
			}

			failure := false
			for i, line := range lines {
				line := strings.TrimSpace(line)

				if i == 0 {
					rateFloat, err := strconv.ParseFloat(line, 32)
					if err != nil {
						fmt.Print("could not convert sample-rate to float")
						fmt.Println(err)
						failure = true
						break
					}
					parsedData.SampleRate = strconv.FormatFloat(rateFloat/1000, 'f', -1, 32) + "KHz"
				} else if i == 1 {
					parsedData.BPS = line
				}
			}

			if !failure {
				return true
			}
		}
	}

	fmt.Println("failed to find file for sampling info")
	return false
}

func generateWikifile(filepath string, foldername string, regex *regexp.Regexp, wikiTemplate *template.Template, deleteMode bool, outBasepath string) {
	basepath := fpath.Join(filepath, foldername)
	infofile := basepath + ".txt"
	wikifile := filepath
	if outBasepath != "" {
		wikifile = outBasepath
	}

	if deleteMode {
		fmt.Printf("Deleting wiki from %s...", infofile)
	} else {
		fmt.Printf("Generating from %s... ", infofile)
	}

	infobytes, err := ioutil.ReadFile(infofile)
	if err != nil {
		if os.IsNotExist(err) {
			fmt.Println("infofile doesn't exist")
		} else {
			fmt.Printf("error (%s)\n", err.Error())
		}
		return
	}

	matches := regex.FindSubmatch(infobytes)
	if len(matches) != 1+regex.NumSubexp() {
		// (entire string itself)+(capture groups)
		fmt.Printf("parse failure, expected %d capturing groups!\n", 1+regex.NumSubexp())
		return
	}

	var parsedData WikiAlbumData
	parsedData.FolderName = foldername

	size, err := getDirectorySize(filepath)
	if err != nil {
		fmt.Println("failed to get directory size")
		fmt.Println(err)
		return
	}
	b := bytesize.New(size)
	parsedData.Size = b.String()

	if !wikiGetInfoFromFlac(filepath, &parsedData) {
		return
	}

	tracks := make([]WikiTrackData, 0)

	var lastTrack WikiTrackData
	var currentTrackNumber int = 0

	for i, field := range matches {
		if i == 0 {
			// The first one is just itself
			continue
		}

		field := string(bytes.TrimSpace(field))

		switch i {
		case 1:
			if field != "" {
				parsedData.Lineage = "*" + field + "\n"
			}
		case 2:
			lineage := ""
			for _, item := range strings.Split(field, "\n") {
				lineage += "*" + strings.TrimSpace(item) + "\r\n"
			}
			parsedData.Lineage += lineage
		case 3:
			parsedData.Notes = field
		case 4:
			str, err := url.QueryUnescape(field)
			if err != nil {
				fmt.Println("error unescaping query from url")
				fmt.Println(err.Error())
				return
			}
			wikifile = fpath.Join(wikifile, strings.Replace(strings.Replace(str, "_", " ", -1), "/", "_", -1)) + ".wiki"
			fmt.Printf("\n - %s... ", wikifile)
		case 5:
			// parse tracks
			for _, track := range strings.Split(field, "\n") {
				var trackData WikiTrackData
				trackData.FolderName = foldername
				trackData.LinePrefix = "#"

				str := strings.TrimSpace(track)
				f := strings.Index(str, "[")
				l := strings.Index(str, "]")
				trackData.Duration = str[f+1 : l]

				number := str[:f-2]
				separator := strings.Index(number, ".")
				if separator != -1 {
					// This bit only uses the "path" library
					// because URL's only use forward slash
					cdStr := number[:separator]

					cdNumber, err := strconv.Atoi(cdStr)
					if err != nil {
						panic(err.Error())
					}
					trackData.FolderName = upath.Join(foldername, "CD"+cdStr)
					trackData.CD = cdNumber

					if lastTrack.CD != cdNumber {
						currentTrackNumber = 0
						trackData.LinePrefix = fmt.Sprintf("\r\nCD%d:\r\n%s", cdNumber, trackData.LinePrefix)
					}
				} else {
					// Here lies incomplete support for "Bonus." tracks
					// you need to add support for using filenames instead
					// of tracknumbers for bonus tracks

					// _, err := strconv.Atoi(number)
					// if err != nil {
					// 	trackData.FolderName = number
					// 	trackData.CD = -1

					// 	if lastTrack.FolderName != number {
					// 		currentTrackNumber = 0
					// 	}
					// }
					// continue
				}

				name := strings.TrimSpace(str[l+1:])
				nameWithoutSuffix := strings.TrimSuffix(name, " (*)")
				trackData.HasAlternateLeadVocalist = name != nameWithoutSuffix
				trackData.Name = nameWithoutSuffix

				currentTrackNumber += 1
				trackData.Index = currentTrackNumber

				lastTrack = trackData
				tracks = append(tracks, trackData)
			}
		case 6:
			parsedData.Duration = field
		}
	}
	parsedData.Tracks = tracks

	success := removeFile(wikifile, false)
	if deleteMode {
		message := "success!"
		if !success {
			message = "couldn't delete!"
		}
		fmt.Println(message)
		return
	} else if success {
		fmt.Print("overwritten... ")
	}

	wikiout := createFile(wikifile)
	defer wikiout.Close()

	if wikiout != nil {
		err = wikiTemplate.Execute(wikiout, parsedData)
		if err != nil {
			fmt.Println("could not insert data into template!")
			fmt.Println(err)
			return
		}

		fmt.Println("success!")
	}
}
<|endoftext|>


<commit_before>/*
 * Copyright 2016 Frank Wessels <fwessels@xs4all.nl>
 *
 * 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 s3git

import (
	"encoding/hex"
	"github.com/bmatsuo/lmdb-go/lmdb"
	"github.com/s3git/s3git-go/internal/backend"
	"github.com/s3git/s3git-go/internal/cas"
	"github.com/s3git/s3git-go/internal/core"
	"github.com/s3git/s3git-go/internal/kv"
	"io/ioutil"
	"os"
)

// Pull updates for the repository
func (repo Repository) Pull(progress func(maxTicks int64)) error {

	return pull(progress)
}

func pull(progress func(maxTicks int64)) error {

	client, err := backend.GetDefaultClient()
	if err != nil {
		return err
	}

	// Get map of prefixes already in store
	prefixesInBackend, err := listPrefixes(client)
	if err != nil {
		return err
	}

	prefixesToFetch := []string{}

	// Iterate over prefixes and find out if they are already available locally
	for prefix, _ := range prefixesInBackend {
		key, _ := hex.DecodeString(prefix)
		_, _, err := kv.GetLevel1(key)
		if err != nil && !lmdb.IsNotFound(err) {
			return err
		}

		if lmdb.IsNotFound(err) {
			// Prefix is not available locally  --> add to get fetched
			prefixesToFetch = append(prefixesToFetch, prefix)
		}
	}

	if len(prefixesToFetch) == 0 {
		return nil
	}

	progress(int64(len(prefixesToFetch)))

	// TODO: [perf] Speed up by running parallel in multiple go routines
	for _, prefix := range prefixesToFetch {

		// Fetch Prefix object and all objects directly and indirectly referenced by it
		err = fetchPrefix(prefix, client)
		if err != nil {
			return err
		}

		progress(int64(len(prefixesToFetch)))
	}

	return nil
}

// Fetch Prefix object and all objects directly and indirectly referenced by it
func fetchPrefix(prefix string, client backend.Backend) error {

	// Make sure that we do the storage of the object in the cas in
	// reverse order, that is store prefix object in the cas as the
	// *last* action, just before that the commit object, etc.
	// This ensures that, if the pull action is prematurely terminated
	// (either by user/application, or some sort of error/crash) that
	// a subsequent pull will still miss the prefix object and pull it
	// again

	// First fetch blob for prefix object
	prefixName, prefixBytes, err := fetchBlobTempFileAndContents(prefix, client)
	if err != nil {
		return err
	}
	defer os.Remove(prefixName)

	po, err := core.GetPrefixObjectFromString(string(prefixBytes))
	if err != nil {
		return err
	}

	{
		// Now pull down commit object
		commitName, commitBytes, err := fetchBlobTempFileAndContents(po.S3gitFollowMe, client)
		if err != nil {
			return err
		}
		defer os.Remove(commitName)

		co, err := core.GetCommitObjectFromString(string(commitBytes))
		if err != nil {
			return err
		}

		{
			// Now pull down tree object
			treeName, treeBytes, err := fetchBlobTempFileAndContents(co.S3gitTree, client)
			if err != nil {
				return err
			}
			defer os.Remove(treeName)

			to, err := core.GetTreeObjectFromString(string(treeBytes))
			if err != nil {
				return err
			}

			// Cache root keys for all added blobs in this commit ...
			err = cacheKeysForBlobs(to.S3gitAdded)
			if err != nil {
				return err
			}

			// Add tree object to cas
			_, err = cas.StoreBlobInCache(treeName, kv.TREE)
			if err != nil {
				return err
			}

			// Delete the chunks for the tree object since we are unlikely to need it again
			err = cas.DeleteLeavesForBlob(co.S3gitTree)
			if err != nil {
				return err
			}
		}

		// Add commit object to cas
		_, err = cas.StoreBlobInCache(commitName, kv.COMMIT)
		if err != nil {
			return err
		}

		// Mark warm and cold parents as parents
		err = co.MarkWarmAndColdParents()
		if err != nil {
			return err
		}
	}

	// Add prefix object to cas
	_, err = cas.StoreBlobInCache(prefixName, kv.PREFIX)
	if err != nil {
		return err
	}

	return nil
}

// Fetch blob down to temp file along with contents
func fetchBlobTempFileAndContents(prefix string, client backend.Backend) (tempFile string, contents []byte, err error) {

	name, err := cas.FetchBlobToTempFile(prefix, client)
	if err != nil {
		return "", nil, err
	}

	contents, err = ioutil.ReadFile(name)
	if err != nil {
		return "", nil, err
	}

	return name, contents, nil
}

const treeBatchSize = 0x4000

// Just cache the key for BLOBs, content will be pulled down later when needed
// For performance do not write per key to the KV but write in larger batches
func cacheKeysForBlobs(added []string) error {

	// Create arrays to be able to batch the operation
	keys := make([][]byte, 0, treeBatchSize)
	values := make([][]byte, 0, treeBatchSize)

	count := 0
	for _, add := range added {
		key, _ := hex.DecodeString(add)

		keys = append(keys, key)
		values = append(values, nil)

		if len(keys) == cap(keys) {
			err := kv.AddMultiToLevel1(keys, values, kv.BLOB)
			if err != nil {
				return err
			}

			keys = keys[:0]
			values = values[:0]
		}
		count++
	}

	if len(keys) > 0 {
		err := kv.AddMultiToLevel1(keys, values, kv.BLOB)
		if err != nil {
			return err
		}
		count++
	}

	return nil
}
<commit_msg>Pull children recursively when pulling a snapshot object<commit_after>/*
 * Copyright 2016 Frank Wessels <fwessels@xs4all.nl>
 *
 * 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 s3git

import (
	"encoding/hex"
	"github.com/bmatsuo/lmdb-go/lmdb"
	"github.com/s3git/s3git-go/internal/backend"
	"github.com/s3git/s3git-go/internal/cas"
	"github.com/s3git/s3git-go/internal/core"
	"github.com/s3git/s3git-go/internal/kv"
	"io/ioutil"
	"os"
)

// Pull updates for the repository
func (repo Repository) Pull(progress func(maxTicks int64)) error {

	return pull(progress)
}

func pull(progress func(maxTicks int64)) error {

	client, err := backend.GetDefaultClient()
	if err != nil {
		return err
	}

	// Get map of prefixes already in store
	prefixesInBackend, err := listPrefixes(client)
	if err != nil {
		return err
	}

	prefixesToFetch := []string{}

	// Iterate over prefixes and find out if they are already available locally
	for prefix, _ := range prefixesInBackend {
		key, _ := hex.DecodeString(prefix)
		_, _, err := kv.GetLevel1(key)
		if err != nil && !lmdb.IsNotFound(err) {
			return err
		}

		if lmdb.IsNotFound(err) {
			// Prefix is not available locally  --> add to get fetched
			prefixesToFetch = append(prefixesToFetch, prefix)
		}
	}

	if len(prefixesToFetch) == 0 {
		return nil
	}

	progress(int64(len(prefixesToFetch)))

	// TODO: [perf] Speed up by running parallel in multiple go routines
	for _, prefix := range prefixesToFetch {

		// Fetch Prefix object and all objects directly and indirectly referenced by it
		err = fetchPrefix(prefix, client)
		if err != nil {
			return err
		}

		progress(int64(len(prefixesToFetch)))
	}

	return nil
}

// Fetch Prefix object and all objects directly and indirectly referenced by it
func fetchPrefix(prefix string, client backend.Backend) error {

	// Make sure that we do the storage of the object in the cas in
	// reverse order, that is store prefix object in the cas as the
	// *last* action, just before that the commit object, etc.
	// This ensures that, if the pull action is prematurely terminated
	// (either by user/application, or some sort of error/crash) that
	// a subsequent pull will still miss the prefix object and pull it
	// again

	// First fetch blob for prefix object
	prefixName, prefixBytes, err := fetchBlobTempFileAndContents(prefix, client)
	if err != nil {
		return err
	}
	defer os.Remove(prefixName)

	po, err := core.GetPrefixObjectFromString(string(prefixBytes))
	if err != nil {
		return err
	}

	{
		// Now pull down commit object
		commitName, commitBytes, err := fetchBlobTempFileAndContents(po.S3gitFollowMe, client)
		if err != nil {
			return err
		}
		defer os.Remove(commitName)

		co, err := core.GetCommitObjectFromString(string(commitBytes))
		if err != nil {
			return err
		}

		if co.S3gitTree != "" {
			// Now pull down tree object
			treeName, treeBytes, err := fetchBlobTempFileAndContents(co.S3gitTree, client)
			if err != nil {
				return err
			}
			defer os.Remove(treeName)

			to, err := core.GetTreeObjectFromString(string(treeBytes))
			if err != nil {
				return err
			}

			// Cache root keys for all added blobs in this commit ...
			err = cacheKeysForBlobs(to.S3gitAdded)
			if err != nil {
				return err
			}

			// Add tree object to cas
			_, err = cas.StoreBlobInCache(treeName, kv.TREE)
			if err != nil {
				return err
			}

			// Delete the chunks for the tree object since we are unlikely to need it again
			err = cas.DeleteLeavesForBlob(co.S3gitTree)
			if err != nil {
				return err
			}
		}

		if co.S3gitSnapshot != "" {

			err = pullSnapshotWithChildren(co.S3gitSnapshot, client)
			if err != nil {
				return err
			}
		}

		// Add commit object to cas
		_, err = cas.StoreBlobInCache(commitName, kv.COMMIT)
		if err != nil {
			return err
		}

		// Mark warm and cold parents as parents
		err = co.MarkWarmAndColdParents()
		if err != nil {
			return err
		}
	}

	// Add prefix object to cas
	_, err = cas.StoreBlobInCache(prefixName, kv.PREFIX)
	if err != nil {
		return err
	}

	return nil
}

func pullSnapshotWithChildren(hash string, client backend.Backend) error {

	// Now pull down snapshot object
	snapshotName, snapshotBytes, err := fetchBlobTempFileAndContents(hash, client)
	if err != nil {
		return err
	}
	defer os.Remove(snapshotName)

	so, err := core.GetSnapshotObjectFromString(string(snapshotBytes))

	for _, entry := range so.S3gitEntries {
		if entry.IsDirectory() {
			err = pullSnapshotWithChildren(entry.Blob, client)
		}
	}

	// Add snapshot object to cas
	_, err = cas.StoreBlobInCache(snapshotName, kv.SNAPSHOT)
	if err != nil {
		return err
	}

	return nil
}

// Fetch blob down to temp file along with contents
func fetchBlobTempFileAndContents(prefix string, client backend.Backend) (tempFile string, contents []byte, err error) {

	name, err := cas.FetchBlobToTempFile(prefix, client)
	if err != nil {
		return "", nil, err
	}

	contents, err = ioutil.ReadFile(name)
	if err != nil {
		return "", nil, err
	}

	return name, contents, nil
}

const treeBatchSize = 0x4000

// Just cache the key for BLOBs, content will be pulled down later when needed
// For performance do not write per key to the KV but write in larger batches
func cacheKeysForBlobs(added []string) error {

	// Create arrays to be able to batch the operation
	keys := make([][]byte, 0, treeBatchSize)
	values := make([][]byte, 0, treeBatchSize)

	count := 0
	for _, add := range added {
		key, _ := hex.DecodeString(add)

		keys = append(keys, key)
		values = append(values, nil)

		if len(keys) == cap(keys) {
			err := kv.AddMultiToLevel1(keys, values, kv.BLOB)
			if err != nil {
				return err
			}

			keys = keys[:0]
			values = values[:0]
		}
		count++
	}

	if len(keys) > 0 {
		err := kv.AddMultiToLevel1(keys, values, kv.BLOB)
		if err != nil {
			return err
		}
		count++
	}

	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"os"
	"os/signal"
	"sync"
	"syscall"
	"time"

	"github.com/doozr/guac"
	"github.com/doozr/jot"
	"github.com/doozr/qbot/command"
	"github.com/doozr/qbot/dispatch"
	"github.com/doozr/qbot/notification"
	"github.com/doozr/qbot/queue"
	"github.com/doozr/qbot/usercache"
)

// Version is the current release version
var Version string

func main() {
	if len(os.Args) < 3 {
		fmt.Println("Usage: qbot <token> <data file>")
		os.Exit(1)
	}

	if Version != "" {
		log.Printf("Qbot version %s", Version)
	} else {
		log.Printf("Qbot <unversioned build>")
	}

	// Get command line parameters
	token := os.Args[1]
	filename := os.Args[2]

	// Turn on jot if required
	if os.Getenv("QBOT_DEBUG") == "true" {
		jot.Enable()
	}

	// Synchronisation primitives
	waitGroup := sync.WaitGroup{}
	done := make(chan struct{})

	// Connect to Slack
	client, err := guac.New(token).RealTime()
	if err != nil {
		log.Fatal("Error connecting to Slack ", err)
	}
	log.Print("Connected to slack as ", client.Name())

	// Instantiate state
	userCache := getUserList(client)
	name := client.Name()
	jot.Print("qbot: name is ", name)
	q := loadQueue(filename)

	// Set up command and response processors
	notifications := notification.New(userCache)
	commands := command.New(notifications, userCache)

	// Create dispatchers
	notify := dispatch.NewNotifier(client)
	persist := dispatch.NewPersister(filename)
	messageHandler := dispatch.NewMessageHandler(client.ID(), client.Name(), q, commands, notify, persist)
	userChangeHandler := dispatch.NewUserChangeHandler(userCache)

	// keepalive
	waitGroup.Add(1)
	go keepalive(client, done, &waitGroup)

	// Dispatch incoming events
	jot.Println("qbot: ready to receive events")
	dispatcher := dispatch.New(messageHandler, userChangeHandler)
	abort := dispatcher.Listen(client, 2*time.Minute, done, &waitGroup)

	// Wait for signals to stop
	sig := addSignalHandler()

	// Wait for a signal
	select {
	case err = <-abort:
		if err != nil {
			log.Print("Error: ", err)
		}
		log.Print("Execution terminated - shutting down")
	case s := <-sig:
		log.Printf("Received %s signal - shutting down", s)
	}

	jot.Print("qbot: closing done channel")
	close(done)

	jot.Print("qbot: closing connection")
	client.Close()

	jot.Print("qbot: waiting for dispatch to terminate")
	waitGroup.Wait()

	jot.Print("qbot: shutdown complete")
}

func addSignalHandler() chan os.Signal {
	sig := make(chan os.Signal, 1)
	signal.Notify(sig, syscall.SIGINT)
	signal.Notify(sig, syscall.SIGTERM)
	signal.Notify(sig, syscall.SIGKILL)
	return sig
}

func getUserList(client guac.WebClient) (userCache *usercache.UserCache) {
	log.Println("Getting user list")
	users, err := client.UsersList()
	if err != nil {
		log.Fatal(err)
	}
	userCache = usercache.New(users)
	jot.Print("loaded user list: ", userCache)
	return
}

func loadQueue(filename string) (q queue.Queue) {
	q, err := queue.Load(filename)
	if err != nil {
		log.Fatalf("Error loading queue: %s", err)
	}
	log.Printf("Loaded queue from %s", filename)
	return
}
<commit_msg>1 minute timeout<commit_after>package main

import (
	"fmt"
	"log"
	"os"
	"os/signal"
	"sync"
	"syscall"
	"time"

	"github.com/doozr/guac"
	"github.com/doozr/jot"
	"github.com/doozr/qbot/command"
	"github.com/doozr/qbot/dispatch"
	"github.com/doozr/qbot/notification"
	"github.com/doozr/qbot/queue"
	"github.com/doozr/qbot/usercache"
)

// Version is the current release version
var Version string

func main() {
	if len(os.Args) < 3 {
		fmt.Println("Usage: qbot <token> <data file>")
		os.Exit(1)
	}

	if Version != "" {
		log.Printf("Qbot version %s", Version)
	} else {
		log.Printf("Qbot <unversioned build>")
	}

	// Get command line parameters
	token := os.Args[1]
	filename := os.Args[2]

	// Turn on jot if required
	if os.Getenv("QBOT_DEBUG") == "true" {
		jot.Enable()
	}

	// Synchronisation primitives
	waitGroup := sync.WaitGroup{}
	done := make(chan struct{})

	// Connect to Slack
	client, err := guac.New(token).RealTime()
	if err != nil {
		log.Fatal("Error connecting to Slack ", err)
	}
	log.Print("Connected to slack as ", client.Name())

	// Instantiate state
	userCache := getUserList(client)
	name := client.Name()
	jot.Print("qbot: name is ", name)
	q := loadQueue(filename)

	// Set up command and response processors
	notifications := notification.New(userCache)
	commands := command.New(notifications, userCache)

	// Create dispatchers
	notify := dispatch.NewNotifier(client)
	persist := dispatch.NewPersister(filename)
	messageHandler := dispatch.NewMessageHandler(client.ID(), client.Name(), q, commands, notify, persist)
	userChangeHandler := dispatch.NewUserChangeHandler(userCache)

	// keepalive
	waitGroup.Add(1)
	go keepalive(client, done, &waitGroup)

	// Dispatch incoming events
	jot.Println("qbot: ready to receive events")
	dispatcher := dispatch.New(messageHandler, userChangeHandler)
	abort := dispatcher.Listen(client, 1*time.Minute, done, &waitGroup)

	// Wait for signals to stop
	sig := addSignalHandler()

	// Wait for a signal
	select {
	case err = <-abort:
		if err != nil {
			log.Print("Error: ", err)
		}
		log.Print("Execution terminated - shutting down")
	case s := <-sig:
		log.Printf("Received %s signal - shutting down", s)
	}

	jot.Print("qbot: closing done channel")
	close(done)

	jot.Print("qbot: closing connection")
	client.Close()

	jot.Print("qbot: waiting for dispatch to terminate")
	waitGroup.Wait()

	jot.Print("qbot: shutdown complete")
}

func addSignalHandler() chan os.Signal {
	sig := make(chan os.Signal, 1)
	signal.Notify(sig, syscall.SIGINT)
	signal.Notify(sig, syscall.SIGTERM)
	signal.Notify(sig, syscall.SIGKILL)
	return sig
}

func getUserList(client guac.WebClient) (userCache *usercache.UserCache) {
	log.Println("Getting user list")
	users, err := client.UsersList()
	if err != nil {
		log.Fatal(err)
	}
	userCache = usercache.New(users)
	jot.Print("loaded user list: ", userCache)
	return
}

func loadQueue(filename string) (q queue.Queue) {
	q, err := queue.Load(filename)
	if err != nil {
		log.Fatalf("Error loading queue: %s", err)
	}
	log.Printf("Loaded queue from %s", filename)
	return
}
<|endoftext|>


<commit_before>package proxmox

import (
	"errors"
	"net/url"
	"strconv"
	"strings"
	"time"

	_ "github.com/davecgh/go-spew/spew"
)

type QemuVM struct {
	Mem       float64 `json:"mem"`
	CPUs      float64 `json:"cpus"`
	NetOut    float64 `json:"netout"`
	PID       string  `json:"pid"`
	Disk      float64 `json:"disk"`
	MaxMem    float64 `json:"maxmem"`
	Status    string  `json:"status"`
	Template  float64 `json:"template"`
	NetIn     float64 `json:"netin"`
	MaxDisk   float64 `json:"maxdisk"`
	Name      string  `json:"name"`
	DiskWrite float64 `json:"diskwrite"`
	CPU       float64 `json:"cpu"`
	VMId      float64 `json:"vmid"`
	DiskRead  float64 `json:"diskread"`
	Uptime    float64 `json:"uptime"`
	Node      Node
}

type QemuList map[string]QemuVM

type QemuNet map[string]string

type QemuConfig struct {
	Bootdisk    string  `json:"bootdisk"`
	Cores       float64 `json:"cores"`
	Digest      string  `json:"digest"`
	Memory      float64 `json:"memory"`
	Net         map[string]QemuNet
	SMBios1     string            `json:"smbios1"`
	Sockets     float64           `json:"sockets"`
	Disks       map[string]string `json:"disks"`
	Description string            `json:"description"`
}

type QemuStatus struct {
	CPU       float64 `json:"cpu"`
	CPUs      float64 `json:"cpus"`
	Mem       float64 `json:"mem"`
	MaxMem    float64 `json:"maxmem"`
	Disk      float64 `json:"disk"`
	MaxDisk   float64 `json:"maxdisk"`
	DiskWrite float64 `json:"diskwrite"`
	DiskRead  float64 `json:"diskread"`
	NetIn     float64 `json:"netin"`
	NetOut    float64 `json:"netout"`
	Uptime    float64 `json:"uptime"`
	QmpStatus string  `json:"qmpstatus"`
	Status    string  `json:"status"`
	Template  string  `json:"template"`
}

func (qemu QemuVM) Delete() (map[string]interface{}, error) {
	var target string
	var data map[string]interface{}
	var err error

	//fmt.Print("!QemuDelete ", qemu.VMId)

	target = "nodes/" + qemu.Node.Node + "/qemu/" + strconv.FormatFloat(qemu.VMId, 'f', 0, 64)
	data, err = qemu.Node.Proxmox.Delete(target)
	if err != nil {
		return nil, err
	}
	return data, nil
}

func stringToMap(data string, itemSeparator string, kvSeparator string) map[string]string {
	var result map[string]string

	result = make(map[string]string)
	list := strings.Split(data, itemSeparator)
	for _, item := range list {
		kv := strings.Split(item, kvSeparator)
		result[kv[0]] = kv[1]
	}
	return result
}

func (qemu QemuVM) Config() (QemuConfig, error) {
	var target string
	var data map[string]interface{}
	var results map[string]interface{}
	var config QemuConfig
	var err error

	//fmt.Print("!QemuConfig ", qemu.VMId)

	target = "nodes/" + qemu.Node.Node + "/qemu/" + strconv.FormatFloat(qemu.VMId, 'f', 0, 64) + "/config"
	data, err = qemu.Node.Proxmox.Get(target)
	results = data["data"].(map[string]interface{})
	if err != nil {
		return config, err
	}
	config = QemuConfig{
		Bootdisk:    results["bootdisk"].(string),
		Cores:       results["cores"].(float64),
		Digest:      results["digest"].(string),
		Memory:      results["memory"].(float64),
		Sockets:     results["sockets"].(float64),
		SMBios1:     results["smbios1"].(string),
		Description: results["description"].(string),
	}
	disktype := [3]string{"virtio", "sata", "ide"}
	disknum := [4]string{"0", "1", "2", "3"}
	config.Disks = make(map[string]string)
	for _, d := range disktype {
		for _, i := range disknum {
			id := d + i
			if disk, ok := results[id]; ok {
				config.Disks[id] = disk.(string)
			}
		}
	}
	config.Net = make(map[string]QemuNet)
	netnum := [4]string{"0", "1", "2", "3"}
	for _, n := range netnum {
		if net, ok := results["net"+n]; ok {
			config.Net["net"+n] = stringToMap(net.(string), ",", "=")
		}
	}

	return config, nil
}

func (qemu QemuVM) CurrentStatus() (QemuStatus, error) {
	var target string
	var err error
	var data map[string]interface{}
	var results map[string]interface{}
	var status QemuStatus

	//fmt.Println("!QemuStatus ", strconv.FormatFloat(qemu.VMId, 'f', 0, 64))

	target = "nodes/" + qemu.Node.Node + "/qemu/" + strconv.FormatFloat(qemu.VMId, 'f', 0, 64) + "/status/current"
	data, err = qemu.Node.Proxmox.Get(target)
	if err != nil {
		return status, err
	}
	results = data["data"].(map[string]interface{})
	status = QemuStatus{
		CPU:       results["cpu"].(float64),
		CPUs:      results["cpus"].(float64),
		Mem:       results["mem"].(float64),
		MaxMem:    results["maxmem"].(float64),
		Disk:      results["disk"].(float64),
		MaxDisk:   results["maxdisk"].(float64),
		DiskWrite: results["diskwrite"].(float64),
		DiskRead:  results["diskread"].(float64),
		NetIn:     results["netin"].(float64),
		NetOut:    results["netout"].(float64),
		Uptime:    results["uptime"].(float64),
		QmpStatus: results["qmpstatus"].(string),
		Status:    results["status"].(string),
		Template:  results["template"].(string),
	}
	return status, nil
}

func (qemu QemuVM) WaitForStatus(status string, timeout int) error {
	var i int
	var err error
	var qStatus QemuStatus
	for i = 0; i < timeout; i++ {
		qStatus, err = qemu.CurrentStatus()
		if err != nil {
			return err
		}

		if qStatus.Status == status {
			return nil
		}
		time.Sleep(time.Second * 1)
	}
	return errors.New("Timeout reached")
}

func (qemu QemuVM) Start() error {
	var target string
	var err error

	//fmt.Println("!QemuStart ", strconv.FormatFloat(qemu.VMId, 'f', 0, 64))

	target = "nodes/" + qemu.Node.Node + "/qemu/" + strconv.FormatFloat(qemu.VMId, 'f', 0, 64) + "/status/start"
	_, err = qemu.Node.Proxmox.Post(target, "")
	return err
}

func (qemu QemuVM) Stop() error {
	var target string
	var err error

	//fmt.Print("!QemuStop ", qemu.VMId)

	target = "nodes/" + qemu.Node.Node + "/qemu/" + strconv.FormatFloat(qemu.VMId, 'f', 0, 64) + "/status/stop"
	_, err = qemu.Node.Proxmox.Post(target, "")
	return err
}

func (qemu QemuVM) Shutdown() error {
	var target string
	var err error

	//fmt.Print("!QemuShutdown ", qemu.VMId)

	target = "nodes/" + qemu.Node.Node + "/qemu/" + strconv.FormatFloat(qemu.VMId, 'f', 0, 64) + "/status/shutdown"
	_, err = qemu.Node.Proxmox.Post(target, "")
	return err
}

func (qemu QemuVM) Suspend() error {
	var target string
	var err error

	//fmt.Print("!QemuSuspend ", qemu.VMId)

	target = "nodes/" + qemu.Node.Node + "/qemu/" + strconv.FormatFloat(qemu.VMId, 'f', 0, 64) + "/status/suspend"
	_, err = qemu.Node.Proxmox.Post(target, "")
	return err
}

func (qemu QemuVM) Resume() error {
	var target string
	var err error

	//fmt.Print("!QemuResume ", qemu.VMId)

	target = "nodes/" + qemu.Node.Node + "/qemu/" + strconv.FormatFloat(qemu.VMId, 'f', 0, 64) + "/status/resume"
	_, err = qemu.Node.Proxmox.Post(target, "")
	return err
}

func (qemu QemuVM) Clone(newId float64, name string, targetName string) (Task, error) {
	var target string
	var err error

	newVMID := strconv.FormatFloat(newId, 'f', 0, 64)

	target = "nodes/" + qemu.Node.Node + "/qemu/" + strconv.FormatFloat(qemu.VMId, 'f', 0, 64) + "/clone"

	form := url.Values{
		"newid":  {newVMID},
		"name":   {name},
		"target": {targetName},
		"full":   {"1"},
	}

	data, err := qemu.Node.Proxmox.PostForm(target, form)
	if err != err {
		return Task{}, err
	}

	t := Task{
		UPid:    data["data"].(string),
		proxmox: qemu.Node.Proxmox,
	}

	return t, nil
}

func (qemu QemuVM) SetIPSet(ip string) error {
	var target string
	var err error

	target = "nodes/" + qemu.Node.Node + "/qemu/" + strconv.FormatFloat(qemu.VMId, 'f', 0, 64) + "/firewall/options"

	form := url.Values{
		"dhcp":          {"1"},
		"enable":        {"1"},
		"log_level_in":  {"nolog"},
		"log_level_out": {"nolog"},
		"macfilter":     {"1"},
		"ipfilter":      {"1"},
		"ndp":           {"1"},
		"policy_in":     {"ACCEPT"},
		"policy_out":    {"ACCEPT"},
	}

	_, err = qemu.Node.Proxmox.PutForm(target, form)
	if err != nil {
		return err
	}

	target = "nodes/" + qemu.Node.Node + "/qemu/" + strconv.FormatFloat(qemu.VMId, 'f', 0, 64) + "/firewall/ipset"

	form = url.Values{
		"name": {"ipfilter-net0"},
	}

	_, err = qemu.Node.Proxmox.PostForm(target, form)
	if err != nil {
		return err
	}

	target = "nodes/" + qemu.Node.Node + "/qemu/" + strconv.FormatFloat(qemu.VMId, 'f', 0, 64) + "/firewall/ipset/ipfilter-net0"

	form = url.Values{
		"cidr": {ip},
	}

	_, err = qemu.Node.Proxmox.PostForm(target, form)
	if err != nil {
		return err
	}

	config, err := qemu.Config()
	if err != nil {
		return err
	}

	target = "nodes/" + qemu.Node.Node + "/qemu/" + strconv.FormatFloat(qemu.VMId, 'f', 0, 64) + "/config"

	net := ""

	for k, v := range config.Net["net0"] {
		net += k + "=" + v + ","
	}

	form = url.Values{
		"net0": {net + ",firewall=1"},
	}

	_, err = qemu.Node.Proxmox.PutForm(target, form)
	if err != nil {
		return err
	}

	return nil
}

func (qemu QemuVM) ResizeDisk(size string) error {
	var target string
	var err error

	target = "nodes/" + qemu.Node.Node + "/qemu/" + strconv.FormatFloat(qemu.VMId, 'f', 0, 64) + "/resize"

	form := url.Values{
		"disk": {"scsi1"},
		"size": {size + "G"},
	}

	_, err = qemu.Node.Proxmox.PutForm(target, form)
	if err != nil {
		return err
	}

	return nil
}
<commit_msg>add tasks to shutdown command<commit_after>package proxmox

import (
	"errors"
	"net/url"
	"strconv"
	"strings"
	"time"

	_ "github.com/davecgh/go-spew/spew"
)

type QemuVM struct {
	Mem       float64 `json:"mem"`
	CPUs      float64 `json:"cpus"`
	NetOut    float64 `json:"netout"`
	PID       string  `json:"pid"`
	Disk      float64 `json:"disk"`
	MaxMem    float64 `json:"maxmem"`
	Status    string  `json:"status"`
	Template  float64 `json:"template"`
	NetIn     float64 `json:"netin"`
	MaxDisk   float64 `json:"maxdisk"`
	Name      string  `json:"name"`
	DiskWrite float64 `json:"diskwrite"`
	CPU       float64 `json:"cpu"`
	VMId      float64 `json:"vmid"`
	DiskRead  float64 `json:"diskread"`
	Uptime    float64 `json:"uptime"`
	Node      Node
}

type QemuList map[string]QemuVM

type QemuNet map[string]string

type QemuConfig struct {
	Bootdisk    string  `json:"bootdisk"`
	Cores       float64 `json:"cores"`
	Digest      string  `json:"digest"`
	Memory      float64 `json:"memory"`
	Net         map[string]QemuNet
	SMBios1     string            `json:"smbios1"`
	Sockets     float64           `json:"sockets"`
	Disks       map[string]string `json:"disks"`
	Description string            `json:"description"`
}

type QemuStatus struct {
	CPU       float64 `json:"cpu"`
	CPUs      float64 `json:"cpus"`
	Mem       float64 `json:"mem"`
	MaxMem    float64 `json:"maxmem"`
	Disk      float64 `json:"disk"`
	MaxDisk   float64 `json:"maxdisk"`
	DiskWrite float64 `json:"diskwrite"`
	DiskRead  float64 `json:"diskread"`
	NetIn     float64 `json:"netin"`
	NetOut    float64 `json:"netout"`
	Uptime    float64 `json:"uptime"`
	QmpStatus string  `json:"qmpstatus"`
	Status    string  `json:"status"`
	Template  string  `json:"template"`
}

func (qemu QemuVM) Delete() (map[string]interface{}, error) {
	var target string
	var data map[string]interface{}
	var err error

	//fmt.Print("!QemuDelete ", qemu.VMId)

	target = "nodes/" + qemu.Node.Node + "/qemu/" + strconv.FormatFloat(qemu.VMId, 'f', 0, 64)
	data, err = qemu.Node.Proxmox.Delete(target)
	if err != nil {
		return nil, err
	}
	return data, nil
}

func stringToMap(data string, itemSeparator string, kvSeparator string) map[string]string {
	var result map[string]string

	result = make(map[string]string)
	list := strings.Split(data, itemSeparator)
	for _, item := range list {
		kv := strings.Split(item, kvSeparator)
		result[kv[0]] = kv[1]
	}
	return result
}

func (qemu QemuVM) Config() (QemuConfig, error) {
	var target string
	var data map[string]interface{}
	var results map[string]interface{}
	var config QemuConfig
	var err error

	//fmt.Print("!QemuConfig ", qemu.VMId)

	target = "nodes/" + qemu.Node.Node + "/qemu/" + strconv.FormatFloat(qemu.VMId, 'f', 0, 64) + "/config"
	data, err = qemu.Node.Proxmox.Get(target)
	results = data["data"].(map[string]interface{})
	if err != nil {
		return config, err
	}
	config = QemuConfig{
		Bootdisk:    results["bootdisk"].(string),
		Cores:       results["cores"].(float64),
		Digest:      results["digest"].(string),
		Memory:      results["memory"].(float64),
		Sockets:     results["sockets"].(float64),
		SMBios1:     results["smbios1"].(string),
		Description: results["description"].(string),
	}
	disktype := [3]string{"virtio", "sata", "ide"}
	disknum := [4]string{"0", "1", "2", "3"}
	config.Disks = make(map[string]string)
	for _, d := range disktype {
		for _, i := range disknum {
			id := d + i
			if disk, ok := results[id]; ok {
				config.Disks[id] = disk.(string)
			}
		}
	}
	config.Net = make(map[string]QemuNet)
	netnum := [4]string{"0", "1", "2", "3"}
	for _, n := range netnum {
		if net, ok := results["net"+n]; ok {
			config.Net["net"+n] = stringToMap(net.(string), ",", "=")
		}
	}

	return config, nil
}

func (qemu QemuVM) CurrentStatus() (QemuStatus, error) {
	var target string
	var err error
	var data map[string]interface{}
	var results map[string]interface{}
	var status QemuStatus

	//fmt.Println("!QemuStatus ", strconv.FormatFloat(qemu.VMId, 'f', 0, 64))

	target = "nodes/" + qemu.Node.Node + "/qemu/" + strconv.FormatFloat(qemu.VMId, 'f', 0, 64) + "/status/current"
	data, err = qemu.Node.Proxmox.Get(target)
	if err != nil {
		return status, err
	}
	results = data["data"].(map[string]interface{})
	status = QemuStatus{
		CPU:       results["cpu"].(float64),
		CPUs:      results["cpus"].(float64),
		Mem:       results["mem"].(float64),
		MaxMem:    results["maxmem"].(float64),
		Disk:      results["disk"].(float64),
		MaxDisk:   results["maxdisk"].(float64),
		DiskWrite: results["diskwrite"].(float64),
		DiskRead:  results["diskread"].(float64),
		NetIn:     results["netin"].(float64),
		NetOut:    results["netout"].(float64),
		Uptime:    results["uptime"].(float64),
		QmpStatus: results["qmpstatus"].(string),
		Status:    results["status"].(string),
		Template:  results["template"].(string),
	}
	return status, nil
}

func (qemu QemuVM) WaitForStatus(status string, timeout int) error {
	var i int
	var err error
	var qStatus QemuStatus
	for i = 0; i < timeout; i++ {
		qStatus, err = qemu.CurrentStatus()
		if err != nil {
			return err
		}

		if qStatus.Status == status {
			return nil
		}
		time.Sleep(time.Second * 1)
	}
	return errors.New("Timeout reached")
}

func (qemu QemuVM) Start() error {
	var target string
	var err error

	//fmt.Println("!QemuStart ", strconv.FormatFloat(qemu.VMId, 'f', 0, 64))

	target = "nodes/" + qemu.Node.Node + "/qemu/" + strconv.FormatFloat(qemu.VMId, 'f', 0, 64) + "/status/start"
	_, err = qemu.Node.Proxmox.Post(target, "")
	return err
}

func (qemu QemuVM) Stop() error {
	var target string
	var err error

	//fmt.Print("!QemuStop ", qemu.VMId)

	target = "nodes/" + qemu.Node.Node + "/qemu/" + strconv.FormatFloat(qemu.VMId, 'f', 0, 64) + "/status/stop"
	_, err = qemu.Node.Proxmox.Post(target, "")
	return err
}

func (qemu QemuVM) Shutdown() (Task, error) {
	var target string
	var err error

	//fmt.Print("!QemuShutdown ", qemu.VMId)

	target = "nodes/" + qemu.Node.Node + "/qemu/" + strconv.FormatFloat(qemu.VMId, 'f', 0, 64) + "/status/shutdown"
	data, err = qemu.Node.Proxmox.Post(target, "")

	if err != err {
		return Task{}, err
	}

	t := Task{
		UPid:    data["data"].(string),
		proxmox: qemu.Node.Proxmox,
	}

	return t, err
}

func (qemu QemuVM) Suspend() error {
	var target string
	var err error

	//fmt.Print("!QemuSuspend ", qemu.VMId)

	target = "nodes/" + qemu.Node.Node + "/qemu/" + strconv.FormatFloat(qemu.VMId, 'f', 0, 64) + "/status/suspend"
	_, err = qemu.Node.Proxmox.Post(target, "")
	return err
}

func (qemu QemuVM) Resume() error {
	var target string
	var err error

	//fmt.Print("!QemuResume ", qemu.VMId)

	target = "nodes/" + qemu.Node.Node + "/qemu/" + strconv.FormatFloat(qemu.VMId, 'f', 0, 64) + "/status/resume"
	_, err = qemu.Node.Proxmox.Post(target, "")
	return err
}

func (qemu QemuVM) Clone(newId float64, name string, targetName string) (Task, error) {
	var target string
	var err error

	newVMID := strconv.FormatFloat(newId, 'f', 0, 64)

	target = "nodes/" + qemu.Node.Node + "/qemu/" + strconv.FormatFloat(qemu.VMId, 'f', 0, 64) + "/clone"

	form := url.Values{
		"newid":  {newVMID},
		"name":   {name},
		"target": {targetName},
		"full":   {"1"},
	}

	data, err := qemu.Node.Proxmox.PostForm(target, form)
	if err != err {
		return Task{}, err
	}

	t := Task{
		UPid:    data["data"].(string),
		proxmox: qemu.Node.Proxmox,
	}

	return t, nil
}

func (qemu QemuVM) SetIPSet(ip string) error {
	var target string
	var err error

	target = "nodes/" + qemu.Node.Node + "/qemu/" + strconv.FormatFloat(qemu.VMId, 'f', 0, 64) + "/firewall/options"

	form := url.Values{
		"dhcp":          {"1"},
		"enable":        {"1"},
		"log_level_in":  {"nolog"},
		"log_level_out": {"nolog"},
		"macfilter":     {"1"},
		"ipfilter":      {"1"},
		"ndp":           {"1"},
		"policy_in":     {"ACCEPT"},
		"policy_out":    {"ACCEPT"},
	}

	_, err = qemu.Node.Proxmox.PutForm(target, form)
	if err != nil {
		return err
	}

	target = "nodes/" + qemu.Node.Node + "/qemu/" + strconv.FormatFloat(qemu.VMId, 'f', 0, 64) + "/firewall/ipset"

	form = url.Values{
		"name": {"ipfilter-net0"},
	}

	_, err = qemu.Node.Proxmox.PostForm(target, form)
	if err != nil {
		return err
	}

	target = "nodes/" + qemu.Node.Node + "/qemu/" + strconv.FormatFloat(qemu.VMId, 'f', 0, 64) + "/firewall/ipset/ipfilter-net0"

	form = url.Values{
		"cidr": {ip},
	}

	_, err = qemu.Node.Proxmox.PostForm(target, form)
	if err != nil {
		return err
	}

	config, err := qemu.Config()
	if err != nil {
		return err
	}

	target = "nodes/" + qemu.Node.Node + "/qemu/" + strconv.FormatFloat(qemu.VMId, 'f', 0, 64) + "/config"

	net := ""

	for k, v := range config.Net["net0"] {
		net += k + "=" + v + ","
	}

	form = url.Values{
		"net0": {net + ",firewall=1"},
	}

	_, err = qemu.Node.Proxmox.PutForm(target, form)
	if err != nil {
		return err
	}

	return nil
}

func (qemu QemuVM) ResizeDisk(size string) error {
	var target string
	var err error

	target = "nodes/" + qemu.Node.Node + "/qemu/" + strconv.FormatFloat(qemu.VMId, 'f', 0, 64) + "/resize"

	form := url.Values{
		"disk": {"scsi1"},
		"size": {size + "G"},
	}

	_, err = qemu.Node.Proxmox.PutForm(target, form)
	if err != nil {
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>package xlst

import (
	"errors"
	"io"
	"reflect"
	"regexp"

	"github.com/aymerick/raymond"
	"github.com/tealeg/xlsx"
)

var (
	rgx = regexp.MustCompile(`\{\{\s*(\w+)\.\w+\s*\}\}`)
)

// Xlst Represents template struct
type Xlst struct {
	file   *xlsx.File
	report *xlsx.File
}

// New() creates new Xlst struct and returns pointer to it
func New() *Xlst {
	return &Xlst{}
}

// Render() renders report and stores it in a struct
func (m *Xlst) Render(ctx map[string]interface{}) error {
	report := xlsx.NewFile()
	for i, sheet := range m.file.Sheets {
		report.AddSheet(sheet.Name)
		cloneSheet(sheet, report.Sheets[i])
		for _, row := range sheet.Rows {
			prop := getListProp(row)
			if prop == "" {
				newRow := report.Sheets[0].AddRow()
				cloneRow(row, newRow)
				err := renderRow(newRow, ctx)
				if err != nil {
					return err
				}
				continue
			}
			if !isArray(ctx, prop) {
				newRow := report.Sheets[0].AddRow()
				cloneRow(row, newRow)
				err := renderRow(newRow, ctx)
				if err != nil {
					return err
				}
				continue
			}

			arr := reflect.ValueOf(ctx[prop])
			arrBackup := ctx[prop]
			for i := 0; i < arr.Len(); i++ {
				newRow := report.Sheets[0].AddRow()
				cloneRow(row, newRow)
				ctx[prop] = arr.Index(i).Interface()
				err := renderRow(newRow, ctx)
				if err != nil {
					return err
				}
			}
			ctx[prop] = arrBackup
		}
	}
	m.report = report

	return nil
}

// ReadTemplate() reads template from disk and stores it in a struct
func (m *Xlst) ReadTemplate(path string) error {
	file, err := xlsx.OpenFile(path)
	if err != nil {
		return err
	}
	m.file = file
	return nil
}

// Save() saves generated report to disk
func (m *Xlst) Save(path string) error {
	if m.report == nil {
		return errors.New("Report was not generated")
	}
	return m.report.Save(path)
}

// Write() writes generated report to provided writer
func (m *Xlst) Write(writer io.Writer) error {
	if m.report == nil {
		return errors.New("Report was not generated")
	}
	return m.report.Write(writer)
}

func cloneCell(from, to *xlsx.Cell) {
	to.Value = from.Value
	to.SetStyle(from.GetStyle())
	to.HMerge = from.HMerge
	to.VMerge = from.VMerge
	to.Hidden = from.Hidden
}

func cloneRow(from, to *xlsx.Row) {
	to.Height = from.Height
	for _, cell := range from.Cells {
		newCell := to.AddCell()
		cloneCell(cell, newCell)
	}
}

func renderCell(cell *xlsx.Cell, ctx interface{}) error {
	template, err := raymond.Parse(cell.Value)
	if err != nil {
		return err
	}
	out, err := template.Exec(ctx)
	if err != nil {
		return err
	}
	cell.Value = out
	return nil
}

func cloneSheet(from, to *xlsx.Sheet) {
	for _, col := range from.Cols {
		newCol := xlsx.Col{}
		newCol.SetStyle(col.GetStyle())
		newCol.Width = col.Width
		newCol.Hidden = col.Hidden
		newCol.Collapsed = col.Collapsed
		newCol.Min = col.Min
		newCol.Max = col.Max
		to.Cols = append(to.Cols, &newCol)
	}
}

func isArray(in map[string]interface{}, prop string) bool {
	val, ok := in[prop]
	if !ok {
		return false
	}
	switch reflect.TypeOf(val).Kind() {
	case reflect.Array, reflect.Slice:
		return true
	}
	return false
}

func getListProp(in *xlsx.Row) string {
	for _, cell := range in.Cells {
		if cell.Value == "" {
			continue
		}
		if match := rgx.FindAllStringSubmatch(cell.Value, -1); match != nil {
			return match[0][1]
		}
	}
	return ""
}

func renderRow(in *xlsx.Row, ctx interface{}) error {
	for _, cell := range in.Cells {
		err := renderCell(cell, ctx)
		if err != nil {
			return err
		}
	}
	return nil
}
<commit_msg>Clone cell.NumFmt<commit_after>package xlst

import (
	"errors"
	"io"
	"reflect"
	"regexp"

	"github.com/aymerick/raymond"
	"github.com/tealeg/xlsx"
)

var (
	rgx = regexp.MustCompile(`\{\{\s*(\w+)\.\w+\s*\}\}`)
)

// Xlst Represents template struct
type Xlst struct {
	file   *xlsx.File
	report *xlsx.File
}

// New() creates new Xlst struct and returns pointer to it
func New() *Xlst {
	return &Xlst{}
}

// Render() renders report and stores it in a struct
func (m *Xlst) Render(ctx map[string]interface{}) error {
	report := xlsx.NewFile()
	for i, sheet := range m.file.Sheets {
		report.AddSheet(sheet.Name)
		cloneSheet(sheet, report.Sheets[i])
		for _, row := range sheet.Rows {
			prop := getListProp(row)
			if prop == "" {
				newRow := report.Sheets[0].AddRow()
				cloneRow(row, newRow)
				err := renderRow(newRow, ctx)
				if err != nil {
					return err
				}
				continue
			}
			if !isArray(ctx, prop) {
				newRow := report.Sheets[0].AddRow()
				cloneRow(row, newRow)
				err := renderRow(newRow, ctx)
				if err != nil {
					return err
				}
				continue
			}

			arr := reflect.ValueOf(ctx[prop])
			arrBackup := ctx[prop]
			for i := 0; i < arr.Len(); i++ {
				newRow := report.Sheets[0].AddRow()
				cloneRow(row, newRow)
				ctx[prop] = arr.Index(i).Interface()
				err := renderRow(newRow, ctx)
				if err != nil {
					return err
				}
			}
			ctx[prop] = arrBackup
		}
	}
	m.report = report

	return nil
}

// ReadTemplate() reads template from disk and stores it in a struct
func (m *Xlst) ReadTemplate(path string) error {
	file, err := xlsx.OpenFile(path)
	if err != nil {
		return err
	}
	m.file = file
	return nil
}

// Save() saves generated report to disk
func (m *Xlst) Save(path string) error {
	if m.report == nil {
		return errors.New("Report was not generated")
	}
	return m.report.Save(path)
}

// Write() writes generated report to provided writer
func (m *Xlst) Write(writer io.Writer) error {
	if m.report == nil {
		return errors.New("Report was not generated")
	}
	return m.report.Write(writer)
}

func cloneCell(from, to *xlsx.Cell) {
	to.Value = from.Value
	to.SetStyle(from.GetStyle())
	to.HMerge = from.HMerge
	to.VMerge = from.VMerge
	to.Hidden = from.Hidden
	to.NumFmt = from.NumFmt
}

func cloneRow(from, to *xlsx.Row) {
	to.Height = from.Height
	for _, cell := range from.Cells {
		newCell := to.AddCell()
		cloneCell(cell, newCell)
	}
}

func renderCell(cell *xlsx.Cell, ctx interface{}) error {
	template, err := raymond.Parse(cell.Value)
	if err != nil {
		return err
	}
	out, err := template.Exec(ctx)
	if err != nil {
		return err
	}
	cell.Value = out
	return nil
}

func cloneSheet(from, to *xlsx.Sheet) {
	for _, col := range from.Cols {
		newCol := xlsx.Col{}
		newCol.SetStyle(col.GetStyle())
		newCol.Width = col.Width
		newCol.Hidden = col.Hidden
		newCol.Collapsed = col.Collapsed
		newCol.Min = col.Min
		newCol.Max = col.Max
		to.Cols = append(to.Cols, &newCol)
	}
}

func isArray(in map[string]interface{}, prop string) bool {
	val, ok := in[prop]
	if !ok {
		return false
	}
	switch reflect.TypeOf(val).Kind() {
	case reflect.Array, reflect.Slice:
		return true
	}
	return false
}

func getListProp(in *xlsx.Row) string {
	for _, cell := range in.Cells {
		if cell.Value == "" {
			continue
		}
		if match := rgx.FindAllStringSubmatch(cell.Value, -1); match != nil {
			return match[0][1]
		}
	}
	return ""
}

func renderRow(in *xlsx.Row, ctx interface{}) error {
	for _, cell := range in.Cells {
		err := renderCell(cell, ctx)
		if err != nil {
			return err
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package main

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

	"github.com/go-telegram-bot-api/telegram-bot-api"
)

var debug = flag.Bool("debug", false, "debug")
var noisy = flag.Bool("noisy", false, "noisy")
var keepother = flag.Bool("keepother", false, "keepother")
var ssl = flag.Int("ssl", 0, "0:no ssl, 1:SelfSign, 2:CA-cert")
var webhook = flag.Bool("webhook", false, "webhook mode")
var token = flag.String("token", os.Getenv("YBOTTOKEN"), "token")
var pubip = flag.String("pubip", "", "public ip, get with 'curl -s https://ipinfo.io/ip'")
var port = flag.Int("port", 8443, "webhook server port")
var cert = flag.String("cert", "cert.pem", "cert for webhook https server")
var key = flag.String("key", "key.pem", "priv key for webhook https server")

// path for required bin/util
//var pathAd = flag.String("Ad", func() string { p, _ := exec.LookPath("Ad"); return p }(), "path to Ad")
var path7z = flag.String("7z", func() string { p, _ := exec.LookPath("7z"); return p }(), "path to 7z")
var pathAdoc = flag.String("adoc", func() string { p, _ := exec.LookPath("asciidoctor"); return p }(), "path to asciidoctor")
var pathGs = flag.String("gs", func() string { p, _ := exec.LookPath("gs"); return p }(), "path to gs")

func handleUpdate(bot *tgbotapi.BotAPI, update tgbotapi.Update) {

	if update.Message == nil {
		return
	}
	Noisy := *noisy
	if update.Message.From.UserName == "sehari24jam" {
		Noisy = true
	}

	log.Printf("[%s] %s", update.Message.From.UserName, update.Message.Text)

	msg := tgbotapi.NewMessage(update.Message.Chat.ID, "Failed")
	msg.ReplyToMessageID = update.Message.MessageID

	switch update.Message.Text {
	case "/start":
		msg.Text = fmt.Sprintf("Welcome %s (%s %s).\n"+
			"You may send me asciidoc (.adoc) file.\n"+
			"Or you can pack whole *.adoc and its included images + sub-adoc into a single compressed file.",
			update.Message.From.UserName, update.Message.From.FirstName, update.Message.From.LastName)
		bot.Send(msg)
		return
	default:
		if update.Message.Document == nil {
			msg.Text = "Send me asciidoc file (.adoc). I don't understand: " + update.Message.Text
			bot.Send(msg)
			return
		}
	}

	receiveFile, err := bot.GetFile(tgbotapi.FileConfig{FileID: update.Message.Document.FileID})
	//log.Printf("DocFile: %s", update.Message.Document.FileName)
	if err != nil {
		log.Print(err)
		if Noisy {
			msg.Text = "Failed to proceed uploaded file"
		}
		bot.Send(msg)
		return
	}

	ext := path.Ext(update.Message.Document.FileName)
	packed := false
	doublePacked := false
	unknown := false
	simplepacked := false

	switch strings.ToLower(ext) {

	case ".zip", ".rar", ".7z":
		packed = true
		msg.Text = "Fine, let me try this compressed file"

	case ".tgz", ".tbz2", ".txz":
		packed = true
		doublePacked = true
		msg.Text = "Fine, let me try this compressed file."

	case ".gz", ".bz2", ".xz":
		packed = true
		ftar := strings.TrimSuffix(update.Message.Document.FileName, ext)
		exttar := path.Ext(ftar)
		if exttar == ".tar" {
			doublePacked = true
			msg.Text = "Fine, let me try this compressed file.."
			ext = exttar + ext
		} else if exttar == ".adoc" {
			simplepacked = true
			msg.Text = "Looks good, let me try this compressed file.."
		} else {
			simplepacked = true
			unknown = true
			msg.Text = "Not a compressed document that I knew"
			bot.Send(msg)
		}

	case ".adoc":
		msg.Text = "Looks good, let me work on this file"

	default:
		msg.Text = "Document is not an adoc. I might just keep it for next processing"
		unknown = true
	}

	tmp := path.Join(os.TempDir(), "ybot."+update.Message.Chat.UserName)
	if packed && !simplepacked {
		tmp, err = ioutil.TempDir("", "ybot-")
		if err != nil {
			log.Print(err)
			os.RemoveAll(tmp)
			if Noisy {
				msg.Text = "Unable to create temp"
			}
			bot.Send(msg)
			return
		}
	}

	/// message user we have something
	bot.Send(msg)

	if unknown && !*keepother {
		return
	}

	go func() {
		log.Printf("Starting work on %v", receiveFile)

		workfolder := path.Join(tmp, path.Dir(receiveFile.FilePath))
		//basefile := path.Join(workfolder, strings.TrimSuffix(update.Message.Document.FileName, ext))
		workfile := path.Join(workfolder, update.Message.Document.FileName)
		pdffile := path.Join(workfolder, strings.TrimSuffix(update.Message.Document.FileName, ext)+".pdf")

		// get WorkFile from TG
		response, err := http.Get(receiveFile.Link(*token))
		if err != nil {
			log.Print(err)
			if packed {
				os.RemoveAll(tmp)
			}
			if Noisy {
				msg.Text = "Failed to get uploaded file"
			}
			bot.Send(msg)
			return
		}
		defer response.Body.Close()

		// create sub folder as necessary
		os.MkdirAll(workfolder, os.ModePerm)

		// save WorkFile
		file, err := os.Create(workfile)
		if err != nil {
			log.Print(err)
			if packed {
				os.RemoveAll(tmp)
			}
			if Noisy {
				msg.Text = "Unable to create new file"
			}
			bot.Send(msg)
			return
		}
		// Use io.Copy to just dump the response body to the file. This supports huge files
		_, err = io.Copy(file, response.Body)
		file.Close()
		if err != nil {
			log.Print(err)
			if packed {
				os.RemoveAll(tmp)
			}
			if Noisy {
				msg.Text = "Unable to buffer uploaded file"
			}
			bot.Send(msg)
			return
		}

		///////////////////////////////////// extraction
		switch {
		case doublePacked:
			var out bytes.Buffer
			xcmd1 := exec.Command(*path7z, "x", "-so", "-y", workfile)
			xcmd1.Dir = workfolder
			xcmd2 := exec.Command(*path7z, "x", "-si", "-ttar", "-y")
			xcmd2.Dir = workfolder
			xcmd2.Stdin, _ = xcmd1.StdoutPipe()
			xcmd2.Stdout = &out
			_ = xcmd2.Start()
			_ = xcmd1.Run()
			err := xcmd2.Wait()
			if err != nil {
				log.Print(err)
				if packed {
					os.RemoveAll(tmp)
				}
				if Noisy {
					msg.Text = fmt.Sprintf("Failed %v\n%v", string(out.Bytes()), err)
				}
				bot.Send(msg)
				return
			}
			workfile = path.Join(workfolder, "*.adoc")
			pdffile = path.Join(workfolder, "*.pdf")
		case packed:
			cmd := exec.Command(*path7z, "x", "-y", workfile)
			cmd.Dir = workfolder
			out, err := cmd.CombinedOutput()
			if err != nil {
				log.Print(err)
				if packed {
					os.RemoveAll(tmp)
				}
				if Noisy {
					msg.Text = fmt.Sprintf("Failed %v\n%v", string(out), err)
				}
				bot.Send(msg)
				return
			}
			workfile = path.Join(workfolder, "*.adoc")
			pdffile = path.Join(workfolder, "*.pdf")
		}

		// keep un
		if unknown {
			return
		}

		///////////////////////////////////// conversion
		files, err := filepath.Glob(workfile)
		if err != nil {
			log.Print(err)
			if packed {
				os.RemoveAll(tmp)
			}
			if Noisy {
				msg.Text = fmt.Sprintf("Failed %v.", err)
			}
			bot.Send(msg)
			return
		}

		for _, f := range files {
			log.Printf("Processing %v", f)
			cmd := exec.Command(*pathAdoc, "-a", "allow-uri-read", "-r", "asciidoctor-diagram", "-r", "asciidoctor-pdf", "-b", "pdf", f)
			cmd.Dir = workfolder
			out, err := cmd.CombinedOutput()
			if err != nil {
				log.Print(err)
				if packed {
					os.RemoveAll(tmp)
				}
				if Noisy {
					msg.Text = fmt.Sprintf("Failed %v\n%v", string(out), err)
				}
				bot.Send(msg)
				return
			}
		}

		///////////////////////////////////// optimizing
		files, err = filepath.Glob(pdffile)
		if err != nil {
			log.Print(err)
			if packed {
				os.RemoveAll(tmp)
			}
			if Noisy {
				msg.Text = fmt.Sprintf("Failed %v.", err)
			}
			bot.Send(msg)
			return
		}

		for _, f := range files {
			log.Printf("Optimizing %v", f)
			gcmd := exec.Command(*pathGs, "-q",
				"-dNOPAUSE", "-dBATCH", "-dSAFER", "-dNOOUTERSAVE",
				"-sDEVICE=pdfwrite", "-dCompatibilityLevel=1.4",
				"-dPDFSETTINGS=/prepress", "-dCannotEmbedFontPolicy=/Warning",
				"-dDownsampleColorImages=true", "-dColorImageResolution=300",
				"-dDownsampleGrayImages=true", "-dGrayImageResolution=300",
				"-dDownsampleMonoImages=true", "-dMonoImageResolution=300",
				"-sOutputFile="+f+".pdf", f)
			gcmd.Dir = workfolder
			gout, gerr := gcmd.CombinedOutput()
			if err != nil {
				log.Print(gerr)
				if packed {
					os.RemoveAll(tmp)
				}
				if Noisy {
					msg.Text = fmt.Sprintf("Failed %v\n%v", string(gout), gerr)
				}
				bot.Send(msg)
				return
			}
			err := os.Rename(f+".pdf", f)
			if err != nil {
				log.Print(err)
				if packed {
					os.RemoveAll(tmp)
				}
				if Noisy {
					msg.Text = fmt.Sprintf("Failed %v", err)
				}
				bot.Send(msg)
				return
			}
		}

		///////////////////////////////////// send
		files, err = filepath.Glob(pdffile)
		if err != nil {
			log.Print(err)
			if packed {
				os.RemoveAll(tmp)
			}
			if Noisy {
				msg.Text = fmt.Sprintf("Failed %v..", err)
			}
			bot.Send(msg)
			return
		}

		for _, f := range files {
			bot.Send(tgbotapi.NewDocumentUpload(msg.ChatID, f))
			log.Printf("Send %v", f)
		}
		msg.Text = "Success.."
		bot.Send(msg)
		if packed {
			os.RemoveAll(tmp)
		}
		log.Printf("End of work %v", receiveFile)

	}()

}

func main() {

	flag.Parse()

	//if *token == "" {
	//	*token = os.Getenv("YBOTTOKEN")
	//}
	bot, err := tgbotapi.NewBotAPI(*token)
	if err != nil {
		log.Panic(err)
	}

	bot.Debug = *debug

	log.Printf("Authorized on account %s", bot.Self.UserName)

	if *webhook {

		var updates tgbotapi.UpdatesChannel
		switch *ssl {
		case 1:
			url := fmt.Sprintf("https://%s:%d/%s", *pubip, *port, bot.Token)
			log.Print("Webhook URL: " + url)
			_, err = bot.SetWebhook(tgbotapi.NewWebhookWithCert(url, *cert))
			if err != nil {
				log.Fatal(err)
			}

			updates = bot.ListenForWebhook("/" + bot.Token)
			go http.ListenAndServeTLS(fmt.Sprintf("0.0.0.0:%d", *port), *cert, *key, nil)

		case 2:
			url := fmt.Sprintf("https://%s:%d/%s", *pubip, *port, bot.Token)
			log.Print("Webhook URL: " + url)
			_, err = bot.SetWebhook(tgbotapi.NewWebhook(url))
			if err != nil {
				log.Fatal(err)
			}

			updates = bot.ListenForWebhook("/" + bot.Token)
			go http.ListenAndServeTLS(fmt.Sprintf("0.0.0.0:%d", *port), *cert, *key, nil)

		default:
			url := fmt.Sprintf("http://%s:%d/%s", *pubip, *port, bot.Token)
			log.Print("Webhook URL: " + url)
			_, err = bot.SetWebhook(tgbotapi.NewWebhook(url))
			if err != nil {
				log.Fatal(err)
			}

			updates = bot.ListenForWebhook("/" + bot.Token)
			go http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", *port), nil)
		}

		log.Printf("Starting Collect Update from WebHook")
		for update := range updates {
			handleUpdate(bot, update)
			//log.Printf("%+v\n", update)
		}

	} else {

		u := tgbotapi.NewUpdate(0)
		u.Timeout = 60

		updates, err := bot.GetUpdatesChan(u)

		if err != nil {
			log.Panic(err)
		}

		log.Printf("Starting GetUpdate")
		for update := range updates {
			handleUpdate(bot, update)
		}
	}

}
<commit_msg>versioning<commit_after>package main

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

	"github.com/go-telegram-bot-api/telegram-bot-api"
)

//go:generate bash -c "printf \"package main\\nvar Version = \\\"%s\\\"\\n\" `git describe --tags --long` | tee version.go"

var version = flag.Bool("version", false, "version")
var debug = flag.Bool("debug", false, "debug")
var noisy = flag.Bool("noisy", false, "noisy")
var keepother = flag.Bool("keepother", false, "keepother")
var ssl = flag.Int("ssl", 0, "0:no ssl, 1:SelfSign, 2:CA-cert")
var webhook = flag.Bool("webhook", false, "webhook mode")
var token = flag.String("token", os.Getenv("YBOTTOKEN"), "token")
var pubip = flag.String("pubip", "", "public ip, get with 'curl -s https://ipinfo.io/ip'")
var port = flag.Int("port", 8443, "webhook server port")
var cert = flag.String("cert", "cert.pem", "cert for webhook https server")
var key = flag.String("key", "key.pem", "priv key for webhook https server")

// path for required bin/util
//var pathAd = flag.String("Ad", func() string { p, _ := exec.LookPath("Ad"); return p }(), "path to Ad")
var path7z = flag.String("7z", func() string { p, _ := exec.LookPath("7z"); return p }(), "path to 7z")
var pathAdoc = flag.String("adoc", func() string { p, _ := exec.LookPath("asciidoctor"); return p }(), "path to asciidoctor")
var pathGs = flag.String("gs", func() string { p, _ := exec.LookPath("gs"); return p }(), "path to gs")

func handleUpdate(bot *tgbotapi.BotAPI, update tgbotapi.Update) {

	if update.Message == nil {
		return
	}
	Noisy := *noisy
	if update.Message.From.UserName == "sehari24jam" {
		Noisy = true
	}

	log.Printf("[%s] %s", update.Message.From.UserName, update.Message.Text)

	msg := tgbotapi.NewMessage(update.Message.Chat.ID, "Failed")
	msg.ReplyToMessageID = update.Message.MessageID

	switch update.Message.Text {
	case "/start":
		msg.Text = fmt.Sprintf("Welcome %s (%s %s).\n"+
			"You may send me asciidoc (.adoc) file.\n"+
			"Or you can pack whole *.adoc and its included images + sub-adoc into a single compressed file.",
			update.Message.From.UserName, update.Message.From.FirstName, update.Message.From.LastName)
		bot.Send(msg)
		return
	default:
		if update.Message.Document == nil {
			msg.Text = "Send me asciidoc file (.adoc). I don't understand: " + update.Message.Text
			bot.Send(msg)
			return
		}
	}

	receiveFile, err := bot.GetFile(tgbotapi.FileConfig{FileID: update.Message.Document.FileID})
	//log.Printf("DocFile: %s", update.Message.Document.FileName)
	if err != nil {
		log.Print(err)
		if Noisy {
			msg.Text = "Failed to proceed uploaded file"
		}
		bot.Send(msg)
		return
	}

	ext := path.Ext(update.Message.Document.FileName)
	packed := false
	doublePacked := false
	unknown := false
	simplepacked := false

	switch strings.ToLower(ext) {

	case ".zip", ".rar", ".7z":
		packed = true
		msg.Text = "Fine, let me try this compressed file"

	case ".tgz", ".tbz2", ".txz":
		packed = true
		doublePacked = true
		msg.Text = "Fine, let me try this compressed file."

	case ".gz", ".bz2", ".xz":
		packed = true
		ftar := strings.TrimSuffix(update.Message.Document.FileName, ext)
		exttar := path.Ext(ftar)
		if exttar == ".tar" {
			doublePacked = true
			msg.Text = "Fine, let me try this compressed file.."
			ext = exttar + ext
		} else if exttar == ".adoc" {
			simplepacked = true
			msg.Text = "Looks good, let me try this compressed file.."
		} else {
			simplepacked = true
			unknown = true
			msg.Text = "Not a compressed document that I knew"
			bot.Send(msg)
		}

	case ".adoc":
		msg.Text = "Looks good, let me work on this file"

	default:
		msg.Text = "Document is not an adoc. I might just keep it for next processing"
		unknown = true
	}

	tmp := path.Join(os.TempDir(), "ybot."+update.Message.Chat.UserName)
	if packed && !simplepacked {
		tmp, err = ioutil.TempDir("", "ybot-")
		if err != nil {
			log.Print(err)
			os.RemoveAll(tmp)
			if Noisy {
				msg.Text = "Unable to create temp"
			}
			bot.Send(msg)
			return
		}
	}

	/// message user we have something
	bot.Send(msg)

	if unknown && !*keepother {
		return
	}

	go func() {
		log.Printf("Starting work on %v", receiveFile)

		workfolder := path.Join(tmp, path.Dir(receiveFile.FilePath))
		//basefile := path.Join(workfolder, strings.TrimSuffix(update.Message.Document.FileName, ext))
		workfile := path.Join(workfolder, update.Message.Document.FileName)
		pdffile := path.Join(workfolder, strings.TrimSuffix(update.Message.Document.FileName, ext)+".pdf")

		// get WorkFile from TG
		response, err := http.Get(receiveFile.Link(*token))
		if err != nil {
			log.Print(err)
			if packed {
				os.RemoveAll(tmp)
			}
			if Noisy {
				msg.Text = "Failed to get uploaded file"
			}
			bot.Send(msg)
			return
		}
		defer response.Body.Close()

		// create sub folder as necessary
		os.MkdirAll(workfolder, os.ModePerm)

		// save WorkFile
		file, err := os.Create(workfile)
		if err != nil {
			log.Print(err)
			if packed {
				os.RemoveAll(tmp)
			}
			if Noisy {
				msg.Text = "Unable to create new file"
			}
			bot.Send(msg)
			return
		}
		// Use io.Copy to just dump the response body to the file. This supports huge files
		_, err = io.Copy(file, response.Body)
		file.Close()
		if err != nil {
			log.Print(err)
			if packed {
				os.RemoveAll(tmp)
			}
			if Noisy {
				msg.Text = "Unable to buffer uploaded file"
			}
			bot.Send(msg)
			return
		}

		///////////////////////////////////// extraction
		switch {
		case doublePacked:
			var out bytes.Buffer
			xcmd1 := exec.Command(*path7z, "x", "-so", "-y", workfile)
			xcmd1.Dir = workfolder
			xcmd2 := exec.Command(*path7z, "x", "-si", "-ttar", "-y")
			xcmd2.Dir = workfolder
			xcmd2.Stdin, _ = xcmd1.StdoutPipe()
			xcmd2.Stdout = &out
			_ = xcmd2.Start()
			_ = xcmd1.Run()
			err := xcmd2.Wait()
			if err != nil {
				log.Print(err)
				if packed {
					os.RemoveAll(tmp)
				}
				if Noisy {
					msg.Text = fmt.Sprintf("Failed %v\n%v", string(out.Bytes()), err)
				}
				bot.Send(msg)
				return
			}
			workfile = path.Join(workfolder, "*.adoc")
			pdffile = path.Join(workfolder, "*.pdf")
		case packed:
			cmd := exec.Command(*path7z, "x", "-y", workfile)
			cmd.Dir = workfolder
			out, err := cmd.CombinedOutput()
			if err != nil {
				log.Print(err)
				if packed {
					os.RemoveAll(tmp)
				}
				if Noisy {
					msg.Text = fmt.Sprintf("Failed %v\n%v", string(out), err)
				}
				bot.Send(msg)
				return
			}
			workfile = path.Join(workfolder, "*.adoc")
			pdffile = path.Join(workfolder, "*.pdf")
		}

		// keep un
		if unknown {
			return
		}

		///////////////////////////////////// conversion
		files, err := filepath.Glob(workfile)
		if err != nil {
			log.Print(err)
			if packed {
				os.RemoveAll(tmp)
			}
			if Noisy {
				msg.Text = fmt.Sprintf("Failed %v.", err)
			}
			bot.Send(msg)
			return
		}

		for _, f := range files {
			log.Printf("Processing %v", f)
			cmd := exec.Command(*pathAdoc, "-a", "allow-uri-read", "-r", "asciidoctor-diagram", "-r", "asciidoctor-pdf", "-b", "pdf", f)
			cmd.Dir = workfolder
			out, err := cmd.CombinedOutput()
			if err != nil {
				log.Print(err)
				if packed {
					os.RemoveAll(tmp)
				}
				if Noisy {
					msg.Text = fmt.Sprintf("Failed %v\n%v", string(out), err)
				}
				bot.Send(msg)
				return
			}
		}

		///////////////////////////////////// optimizing
		files, err = filepath.Glob(pdffile)
		if err != nil {
			log.Print(err)
			if packed {
				os.RemoveAll(tmp)
			}
			if Noisy {
				msg.Text = fmt.Sprintf("Failed %v.", err)
			}
			bot.Send(msg)
			return
		}

		for _, f := range files {
			log.Printf("Optimizing %v", f)
			gcmd := exec.Command(*pathGs, "-q",
				"-dNOPAUSE", "-dBATCH", "-dSAFER", "-dNOOUTERSAVE",
				"-sDEVICE=pdfwrite", "-dCompatibilityLevel=1.4",
				"-dPDFSETTINGS=/prepress", "-dCannotEmbedFontPolicy=/Warning",
				"-dDownsampleColorImages=true", "-dColorImageResolution=300",
				"-dDownsampleGrayImages=true", "-dGrayImageResolution=300",
				"-dDownsampleMonoImages=true", "-dMonoImageResolution=300",
				"-sOutputFile="+f+".pdf", f)
			gcmd.Dir = workfolder
			gout, gerr := gcmd.CombinedOutput()
			if err != nil {
				log.Print(gerr)
				if packed {
					os.RemoveAll(tmp)
				}
				if Noisy {
					msg.Text = fmt.Sprintf("Failed %v\n%v", string(gout), gerr)
				}
				bot.Send(msg)
				return
			}
			err := os.Rename(f+".pdf", f)
			if err != nil {
				log.Print(err)
				if packed {
					os.RemoveAll(tmp)
				}
				if Noisy {
					msg.Text = fmt.Sprintf("Failed %v", err)
				}
				bot.Send(msg)
				return
			}
		}

		///////////////////////////////////// send
		files, err = filepath.Glob(pdffile)
		if err != nil {
			log.Print(err)
			if packed {
				os.RemoveAll(tmp)
			}
			if Noisy {
				msg.Text = fmt.Sprintf("Failed %v..", err)
			}
			bot.Send(msg)
			return
		}

		for _, f := range files {
			bot.Send(tgbotapi.NewDocumentUpload(msg.ChatID, f))
			log.Printf("Send %v", f)
		}
		msg.Text = "Success.."
		bot.Send(msg)
		if packed {
			os.RemoveAll(tmp)
		}
		log.Printf("End of work %v", receiveFile)

	}()

}

func main() {

	flag.Parse()

        if *version {
                fmt.Println(Version)
                return
        }

	//if *token == "" {
	//	*token = os.Getenv("YBOTTOKEN")
	//}
	bot, err := tgbotapi.NewBotAPI(*token)
	if err != nil {
		log.Panic(err)
	}

	bot.Debug = *debug

	log.Printf("Authorized on account %s", bot.Self.UserName)

	if *webhook {

		var updates tgbotapi.UpdatesChannel
		switch *ssl {
		case 1:
			url := fmt.Sprintf("https://%s:%d/%s", *pubip, *port, bot.Token)
			log.Print("Webhook URL: " + url)
			_, err = bot.SetWebhook(tgbotapi.NewWebhookWithCert(url, *cert))
			if err != nil {
				log.Fatal(err)
			}

			updates = bot.ListenForWebhook("/" + bot.Token)
			go http.ListenAndServeTLS(fmt.Sprintf("0.0.0.0:%d", *port), *cert, *key, nil)

		case 2:
			url := fmt.Sprintf("https://%s:%d/%s", *pubip, *port, bot.Token)
			log.Print("Webhook URL: " + url)
			_, err = bot.SetWebhook(tgbotapi.NewWebhook(url))
			if err != nil {
				log.Fatal(err)
			}

			updates = bot.ListenForWebhook("/" + bot.Token)
			go http.ListenAndServeTLS(fmt.Sprintf("0.0.0.0:%d", *port), *cert, *key, nil)

		default:
			url := fmt.Sprintf("http://%s:%d/%s", *pubip, *port, bot.Token)
			log.Print("Webhook URL: " + url)
			_, err = bot.SetWebhook(tgbotapi.NewWebhook(url))
			if err != nil {
				log.Fatal(err)
			}

			updates = bot.ListenForWebhook("/" + bot.Token)
			go http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", *port), nil)
		}

		log.Printf("Starting Collect Update from WebHook")
		for update := range updates {
			handleUpdate(bot, update)
			//log.Printf("%+v\n", update)
		}

	} else {

		u := tgbotapi.NewUpdate(0)
		u.Timeout = 60

		updates, err := bot.GetUpdatesChan(u)

		if err != nil {
			log.Panic(err)
		}

		log.Printf("Starting GetUpdate")
		for update := range updates {
			handleUpdate(bot, update)
		}
	}

}
<|endoftext|>