<commit_before>/*
CompileDaemon is a very simple compile daemon for Go.

CompileDaemon watches your .go files in a directory and invokes `go build`
if a file changes.

Examples

In its simplest form, the defaults will do. With the current working directory set
to the source directory you can simply…

    $ CompileDaemon

… and it will recompile your code whenever you save a source file.

If you want it to also run your program each time it builds you might add…

    $ CompileDaemon -command="./MyProgram -my-options"

… and it will also keep a copy of your program running. Killing the old one and
starting a new one each time you build. For advanced usage you can also supply
the changed file to the command by doing…

	$ CompileDaemon -command="./MyProgram -my-options %[1]s"

…but note that this will not be set on the first start.

You may find that you need to exclude some directories and files from
monitoring, such as a .git repository or emacs temporary files…

    $ CompileDaemon -exclude-dir=.git -exclude=".#*"

If you want to monitor files other than .go and .c files you might…

    $ CompileDaemon -include=Makefile -include="*.less" -include="*.tmpl"

Options

There are command line options.

	FILE SELECTION
	-directory=XXX    – Which directory to monitor for changes
	-recursive=XXX    – Look into subdirectories
	-exclude-dir=XXX  – Exclude directories matching glob pattern XXX
	-exlude=XXX       – Exclude files whose basename matches glob pattern XXX
	-include=XXX      – Include files whose basename matches glob pattern XXX
	-pattern=XXX      – Include files whose path matches regexp XXX

	MISC
	-color            - Enable colorized output
	-log-prefix       - Enable/disable stdout/stderr labelling for the child process
	-graceful-kill    - On supported platforms, send the child process a SIGTERM to
	                    allow it to exit gracefully if possible.
	-verbose          - Print information about watched directories.

	ACTIONS
	-build=CCC        – Execute CCC to rebuild when a file changes
	-command=CCC      – Run command CCC after a successful build, stops previous command first

*/
package main

import (
	"bufio"
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"regexp"
	"strings"
	"syscall"
	"time"

	"github.com/fatih/color"
	"gopkg.in/fsnotify.v1"
)

// Milliseconds to wait for the next job to begin after a file change
const WorkDelay = 900

// Default pattern to match files which trigger a build
const FilePattern = `(.+\.go|.+\.c)$`

type globList []string

func (g *globList) String() string {
	return fmt.Sprint(*g)
}
func (g *globList) Set(value string) error {
	*g = append(*g, value)
	return nil
}
func (g *globList) Matches(value string) bool {
	for _, v := range *g {
		if match, err := filepath.Match(v, value); err != nil {
			log.Fatalf("Bad pattern \"%s\": %s", v, err.Error())
		} else if match {
			return true
		}
	}
	return false
}

var (
	flag_directory    = flag.String("directory", ".", "Directory to watch for changes")
	flag_pattern      = flag.String("pattern", FilePattern, "Pattern of watched files")
	flag_command      = flag.String("command", "", "Command to run and restart after build")
	flag_command_stop = flag.Bool("command-stop", false, "Stop command before building")
	flag_recursive    = flag.Bool("recursive", true, "Watch all dirs. recursively")
	flag_build        = flag.String("build", "go build", "Command to rebuild after changes")
	flag_build_dir    = flag.String("build-dir", "", "Directory to run build command in.  Defaults to directory")
	flag_color        = flag.Bool("color", false, "Colorize output for CompileDaemon status messages")
	flag_logprefix    = flag.Bool("log-prefix", true, "Print log timestamps and subprocess stderr/stdout output")
	flag_gracefulkill = flag.Bool("graceful-kill", false, "Gracefully attempt to kill the child process by sending a SIGTERM first")
	flag_verbose      = flag.Bool("verbose", false, "Be verbose about which directories are watched.")

	// initialized in main() due to custom type.
	flag_excludedDirs  globList
	flag_excludedFiles globList
	flag_includedFiles globList
)

func okColor(format string, args ...interface{}) string {
	if *flag_color {
		return color.GreenString(format, args...)
	} else {
		return fmt.Sprintf(format, args...)
	}
}

func failColor(format string, args ...interface{}) string {
	if *flag_color {
		return color.RedString(format, args...)
	} else {
		return fmt.Sprintf(format, args...)
	}
}

// Run `go build` and print the output if something's gone wrong.
func build() bool {
	log.Println(okColor("Running build command!"))

	args := strings.Split(*flag_build, " ")
	if len(args) == 0 {
		// If the user has specified and empty then we are done.
		return true
	}

	cmd := exec.Command(args[0], args[1:]...)

	if *flag_build_dir != "" {
		cmd.Dir = *flag_build_dir
	} else {
		cmd.Dir = *flag_directory
	}

	output, err := cmd.CombinedOutput()

	if err == nil {
		log.Println(okColor("Build ok."))
	} else {
		log.Println(failColor("Error while building:\n"), failColor(string(output)))
	}

	return err == nil
}

func matchesPattern(pattern *regexp.Regexp, file string) bool {
	return pattern.MatchString(file)
}

// Accept build jobs and start building when there are no jobs rushing in.
// The inrush protection is WorkDelay milliseconds long, in this period
// every incoming job will reset the timer.
func builder(jobs <-chan string, buildStarted chan<- string, buildDone chan<- bool) {
	createThreshold := func() <-chan time.Time {
		return time.After(time.Duration(WorkDelay * time.Millisecond))
	}

	threshold := createThreshold()
	eventPath := ""

	for {
		select {
		case eventPath = <-jobs:
			threshold = createThreshold()
		case <-threshold:
			buildStarted <- eventPath
			buildDone <- build()
		}
	}
}

func logger(pipeChan <-chan io.ReadCloser) {
	dumper := func(pipe io.ReadCloser, prefix string) {
		reader := bufio.NewReader(pipe)

	readloop:
		for {
			line, err := reader.ReadString('\n')

			if err != nil {
				break readloop
			}

			if *flag_logprefix {
				log.Print(prefix, " ", line)
			} else {
				log.Print(line)
			}
		}
	}

	for {
		pipe := <-pipeChan
		go dumper(pipe, "stdout:")

		pipe = <-pipeChan
		go dumper(pipe, "stderr:")
	}
}

// Start the supplied command and return stdout and stderr pipes for logging.
func startCommand(command string) (cmd *exec.Cmd, stdout io.ReadCloser, stderr io.ReadCloser, err error) {
	args := strings.Split(command, " ")
	cmd = exec.Command(args[0], args[1:]...)

	if stdout, err = cmd.StdoutPipe(); err != nil {
		err = fmt.Errorf("can't get stdout pipe for command: %s", err)
		return
	}

	if stderr, err = cmd.StderrPipe(); err != nil {
		err = fmt.Errorf("can't get stderr pipe for command: %s", err)
		return
	}

	if err = cmd.Start(); err != nil {
		err = fmt.Errorf("can't start command: %s", err)
		return
	}

	return
}

// Run the command in the given string and restart it after
// a message was received on the buildDone channel.
func runner(commandTemplate string, buildStarted <-chan string, buildSuccess <-chan bool) {
	var currentProcess *os.Process
	pipeChan := make(chan io.ReadCloser)

	go logger(pipeChan)

	go func() {
		sigChan := make(chan os.Signal, 1)
		signal.Notify(sigChan, fatalSignals...)
		<-sigChan
		log.Println(okColor("Received signal, terminating cleanly."))
		if currentProcess != nil {
			killProcess(currentProcess)
		}
		os.Exit(0)
	}()

	for {
		eventPath := <-buildStarted

		// append %0.s to use format specifier even if not supplied by user
		// to suppress warning in returned string.
		command := fmt.Sprintf("%0.s"+commandTemplate, eventPath)

		if !*flag_command_stop {
			if !<-buildSuccess {
				continue
			}
		}

		if currentProcess != nil {
			killProcess(currentProcess)
		}
		if *flag_command_stop {
			log.Println(okColor("Command stopped. Waiting for build to complete."))
			<-buildSuccess
		}

		log.Println(okColor("Restarting the given command."))
		cmd, stdoutPipe, stderrPipe, err := startCommand(command)

		if err != nil {
			log.Fatal(failColor("Could not start command: %s", err))
		}

		pipeChan <- stdoutPipe
		pipeChan <- stderrPipe

		currentProcess = cmd.Process
	}
}

func killProcess(process *os.Process) {
	if *flag_gracefulkill {
		killProcessGracefully(process)
	} else {
		killProcessHard(process)
	}
}

func killProcessHard(process *os.Process) {
	log.Println(okColor("Hard stopping the current process.."))

	if err := process.Kill(); err != nil {
		log.Fatal(failColor("Could not kill child process. Aborting due to danger of infinite forks."))
	}

	if _, err := process.Wait(); err != nil {
		log.Fatal(failColor("Could not wait for child process. Aborting due to danger of infinite forks."))
	}
}

func killProcessGracefully(process *os.Process) {
	done := make(chan error, 1)
	go func() {
		log.Println(okColor("Gracefully stopping the current process.."))
		if err := terminateGracefully(process); err != nil {
			done <- err
			return
		}
		_, err := process.Wait()
		done <- err
	}()

	select {
	case <-time.After(3 * time.Second):
		log.Println(failColor("Could not gracefully stop the current process, proceeding to hard stop."))
		killProcessHard(process)
		<-done
	case err := <-done:
		if err != nil {
			log.Fatal(failColor("Could not kill child process. Aborting due to danger of infinite forks."))
		}
	}
}

func flusher(buildStarted <-chan string, buildSuccess <-chan bool) {
	for {
		<-buildStarted
		<-buildSuccess
	}
}

func main() {
	flag.Var(&flag_excludedDirs, "exclude-dir", " Don't watch directories matching this name")
	flag.Var(&flag_excludedFiles, "exclude", " Don't watch files matching this name")
	flag.Var(&flag_includedFiles, "include", " Watch files matching this name")

	flag.Parse()

	if !*flag_logprefix {
		log.SetFlags(0)
	}

	if *flag_directory == "" {
		fmt.Fprintf(os.Stderr, "-directory=... is required.\n")
		os.Exit(1)
	}

	if *flag_gracefulkill && !gracefulTerminationPossible() {
		log.Fatal("Graceful termination is not supported on your platform.")
	}

	watcher, err := fsnotify.NewWatcher()

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

	defer watcher.Close()

	if *flag_recursive == true {
		err = filepath.Walk(*flag_directory, func(path string, info os.FileInfo, err error) error {
			if err == nil && info.IsDir() {
				if flag_excludedDirs.Matches(info.Name()) {
					return filepath.SkipDir
				} else {
					if *flag_verbose {
						log.Printf("Watching directory '%s' for changes.\n", path)
					}
					return watcher.Add(path)
				}
			}
			return err
		})

		if err != nil {
			log.Fatal("filepath.Walk():", err)
		}

		if err := watcher.Add(*flag_directory); err != nil {
			log.Fatal("watcher.Add():", err)
		}

	} else {
		if err := watcher.Add(*flag_directory); err != nil {
			log.Fatal("watcher.Add():", err)
		}
	}

	pattern := regexp.MustCompile(*flag_pattern)
	jobs := make(chan string)
	buildSuccess := make(chan bool)
	buildStarted := make(chan string)

	go builder(jobs, buildStarted, buildSuccess)

	if *flag_command != "" {
		go runner(*flag_command, buildStarted, buildSuccess)
	} else {
		go flusher(buildStarted, buildSuccess)
	}

	for {
		select {
		case ev := <-watcher.Events:
			if ev.Op&fsnotify.Remove == fsnotify.Remove || ev.Op&fsnotify.Write == fsnotify.Write || ev.Op&fsnotify.Create == fsnotify.Create {
				base := filepath.Base(ev.Name)

				// Assume it is a directory and track it.
				if *flag_recursive == true && !flag_excludedDirs.Matches(ev.Name) {
					watcher.Add(ev.Name)
				}

				if flag_includedFiles.Matches(base) || matchesPattern(pattern, ev.Name) {
					if !flag_excludedFiles.Matches(base) {
						jobs <- ev.Name
					}
				}
			}

		case err := <-watcher.Errors:
			if v, ok := err.(*os.SyscallError); ok {
				if v.Err == syscall.EINTR {
					continue
				}
				log.Fatal("watcher.Error: SyscallError:", v)
			}
			log.Fatal("watcher.Error:", err)
		}
	}
}
<commit_msg>Fix #23<commit_after>/*
CompileDaemon is a very simple compile daemon for Go.

CompileDaemon watches your .go files in a directory and invokes `go build`
if a file changes.

Examples

In its simplest form, the defaults will do. With the current working directory set
to the source directory you can simply…

    $ CompileDaemon

… and it will recompile your code whenever you save a source file.

If you want it to also run your program each time it builds you might add…

    $ CompileDaemon -command="./MyProgram -my-options"

… and it will also keep a copy of your program running. Killing the old one and
starting a new one each time you build. For advanced usage you can also supply
the changed file to the command by doing…

	$ CompileDaemon -command="./MyProgram -my-options %[1]s"

…but note that this will not be set on the first start.

You may find that you need to exclude some directories and files from
monitoring, such as a .git repository or emacs temporary files…

    $ CompileDaemon -exclude-dir=.git -exclude=".#*"

If you want to monitor files other than .go and .c files you might…

    $ CompileDaemon -include=Makefile -include="*.less" -include="*.tmpl"

Options

There are command line options.

	FILE SELECTION
	-directory=XXX    – Which directory to monitor for changes
	-recursive=XXX    – Look into subdirectories
	-exclude-dir=XXX  – Exclude directories matching glob pattern XXX
	-exlude=XXX       – Exclude files whose basename matches glob pattern XXX
	-include=XXX      – Include files whose basename matches glob pattern XXX
	-pattern=XXX      – Include files whose path matches regexp XXX

	MISC
	-color            - Enable colorized output
	-log-prefix       - Enable/disable stdout/stderr labelling for the child process
	-graceful-kill    - On supported platforms, send the child process a SIGTERM to
	                    allow it to exit gracefully if possible.
	-verbose          - Print information about watched directories.

	ACTIONS
	-build=CCC        – Execute CCC to rebuild when a file changes
	-command=CCC      – Run command CCC after a successful build, stops previous command first

*/
package main

import (
	"bufio"
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"regexp"
	"strings"
	"syscall"
	"time"

	"github.com/fatih/color"
	"gopkg.in/fsnotify.v1"
)

// Milliseconds to wait for the next job to begin after a file change
const WorkDelay = 900

// Default pattern to match files which trigger a build
const FilePattern = `(.+\.go|.+\.c)$`

type globList []string

func (g *globList) String() string {
	return fmt.Sprint(*g)
}
func (g *globList) Set(value string) error {
	*g = append(*g, filepath.Clean(value))
	return nil
}
func (g *globList) Matches(value string) bool {
	for _, v := range *g {
		if match, err := filepath.Match(v, value); err != nil {
			log.Fatalf("Bad pattern \"%s\": %s", v, err.Error())
		} else if match {
			return true
		}
	}
	return false
}

var (
	flag_directory    = flag.String("directory", ".", "Directory to watch for changes")
	flag_pattern      = flag.String("pattern", FilePattern, "Pattern of watched files")
	flag_command      = flag.String("command", "", "Command to run and restart after build")
	flag_command_stop = flag.Bool("command-stop", false, "Stop command before building")
	flag_recursive    = flag.Bool("recursive", true, "Watch all dirs. recursively")
	flag_build        = flag.String("build", "go build", "Command to rebuild after changes")
	flag_build_dir    = flag.String("build-dir", "", "Directory to run build command in.  Defaults to directory")
	flag_color        = flag.Bool("color", false, "Colorize output for CompileDaemon status messages")
	flag_logprefix    = flag.Bool("log-prefix", true, "Print log timestamps and subprocess stderr/stdout output")
	flag_gracefulkill = flag.Bool("graceful-kill", false, "Gracefully attempt to kill the child process by sending a SIGTERM first")
	flag_verbose      = flag.Bool("verbose", false, "Be verbose about which directories are watched.")

	// initialized in main() due to custom type.
	flag_excludedDirs  globList
	flag_excludedFiles globList
	flag_includedFiles globList
)

func okColor(format string, args ...interface{}) string {
	if *flag_color {
		return color.GreenString(format, args...)
	} else {
		return fmt.Sprintf(format, args...)
	}
}

func failColor(format string, args ...interface{}) string {
	if *flag_color {
		return color.RedString(format, args...)
	} else {
		return fmt.Sprintf(format, args...)
	}
}

// Run `go build` and print the output if something's gone wrong.
func build() bool {
	log.Println(okColor("Running build command!"))

	args := strings.Split(*flag_build, " ")
	if len(args) == 0 {
		// If the user has specified and empty then we are done.
		return true
	}

	cmd := exec.Command(args[0], args[1:]...)

	if *flag_build_dir != "" {
		cmd.Dir = *flag_build_dir
	} else {
		cmd.Dir = *flag_directory
	}

	output, err := cmd.CombinedOutput()

	if err == nil {
		log.Println(okColor("Build ok."))
	} else {
		log.Println(failColor("Error while building:\n"), failColor(string(output)))
	}

	return err == nil
}

func matchesPattern(pattern *regexp.Regexp, file string) bool {
	return pattern.MatchString(file)
}

// Accept build jobs and start building when there are no jobs rushing in.
// The inrush protection is WorkDelay milliseconds long, in this period
// every incoming job will reset the timer.
func builder(jobs <-chan string, buildStarted chan<- string, buildDone chan<- bool) {
	createThreshold := func() <-chan time.Time {
		return time.After(time.Duration(WorkDelay * time.Millisecond))
	}

	threshold := createThreshold()
	eventPath := ""

	for {
		select {
		case eventPath = <-jobs:
			threshold = createThreshold()
		case <-threshold:
			buildStarted <- eventPath
			buildDone <- build()
		}
	}
}

func logger(pipeChan <-chan io.ReadCloser) {
	dumper := func(pipe io.ReadCloser, prefix string) {
		reader := bufio.NewReader(pipe)

	readloop:
		for {
			line, err := reader.ReadString('\n')

			if err != nil {
				break readloop
			}

			if *flag_logprefix {
				log.Print(prefix, " ", line)
			} else {
				log.Print(line)
			}
		}
	}

	for {
		pipe := <-pipeChan
		go dumper(pipe, "stdout:")

		pipe = <-pipeChan
		go dumper(pipe, "stderr:")
	}
}

// Start the supplied command and return stdout and stderr pipes for logging.
func startCommand(command string) (cmd *exec.Cmd, stdout io.ReadCloser, stderr io.ReadCloser, err error) {
	args := strings.Split(command, " ")
	cmd = exec.Command(args[0], args[1:]...)

	if stdout, err = cmd.StdoutPipe(); err != nil {
		err = fmt.Errorf("can't get stdout pipe for command: %s", err)
		return
	}

	if stderr, err = cmd.StderrPipe(); err != nil {
		err = fmt.Errorf("can't get stderr pipe for command: %s", err)
		return
	}

	if err = cmd.Start(); err != nil {
		err = fmt.Errorf("can't start command: %s", err)
		return
	}

	return
}

// Run the command in the given string and restart it after
// a message was received on the buildDone channel.
func runner(commandTemplate string, buildStarted <-chan string, buildSuccess <-chan bool) {
	var currentProcess *os.Process
	pipeChan := make(chan io.ReadCloser)

	go logger(pipeChan)

	go func() {
		sigChan := make(chan os.Signal, 1)
		signal.Notify(sigChan, fatalSignals...)
		<-sigChan
		log.Println(okColor("Received signal, terminating cleanly."))
		if currentProcess != nil {
			killProcess(currentProcess)
		}
		os.Exit(0)
	}()

	for {
		eventPath := <-buildStarted

		// append %0.s to use format specifier even if not supplied by user
		// to suppress warning in returned string.
		command := fmt.Sprintf("%0.s"+commandTemplate, eventPath)

		if !*flag_command_stop {
			if !<-buildSuccess {
				continue
			}
		}

		if currentProcess != nil {
			killProcess(currentProcess)
		}
		if *flag_command_stop {
			log.Println(okColor("Command stopped. Waiting for build to complete."))
			<-buildSuccess
		}

		log.Println(okColor("Restarting the given command."))
		cmd, stdoutPipe, stderrPipe, err := startCommand(command)

		if err != nil {
			log.Fatal(failColor("Could not start command: %s", err))
		}

		pipeChan <- stdoutPipe
		pipeChan <- stderrPipe

		currentProcess = cmd.Process
	}
}

func killProcess(process *os.Process) {
	if *flag_gracefulkill {
		killProcessGracefully(process)
	} else {
		killProcessHard(process)
	}
}

func killProcessHard(process *os.Process) {
	log.Println(okColor("Hard stopping the current process.."))

	if err := process.Kill(); err != nil {
		log.Fatal(failColor("Could not kill child process. Aborting due to danger of infinite forks."))
	}

	if _, err := process.Wait(); err != nil {
		log.Fatal(failColor("Could not wait for child process. Aborting due to danger of infinite forks."))
	}
}

func killProcessGracefully(process *os.Process) {
	done := make(chan error, 1)
	go func() {
		log.Println(okColor("Gracefully stopping the current process.."))
		if err := terminateGracefully(process); err != nil {
			done <- err
			return
		}
		_, err := process.Wait()
		done <- err
	}()

	select {
	case <-time.After(3 * time.Second):
		log.Println(failColor("Could not gracefully stop the current process, proceeding to hard stop."))
		killProcessHard(process)
		<-done
	case err := <-done:
		if err != nil {
			log.Fatal(failColor("Could not kill child process. Aborting due to danger of infinite forks."))
		}
	}
}

func flusher(buildStarted <-chan string, buildSuccess <-chan bool) {
	for {
		<-buildStarted
		<-buildSuccess
	}
}

func main() {
	flag.Var(&flag_excludedDirs, "exclude-dir", " Don't watch directories matching this name")
	flag.Var(&flag_excludedFiles, "exclude", " Don't watch files matching this name")
	flag.Var(&flag_includedFiles, "include", " Watch files matching this name")

	flag.Parse()

	if !*flag_logprefix {
		log.SetFlags(0)
	}

	if *flag_directory == "" {
		fmt.Fprintf(os.Stderr, "-directory=... is required.\n")
		os.Exit(1)
	}

	if *flag_gracefulkill && !gracefulTerminationPossible() {
		log.Fatal("Graceful termination is not supported on your platform.")
	}

	watcher, err := fsnotify.NewWatcher()

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

	defer watcher.Close()

	if *flag_recursive == true {
		err = filepath.Walk(*flag_directory, func(path string, info os.FileInfo, err error) error {
			if err == nil && info.IsDir() {
				if flag_excludedDirs.Matches(path) {
					return filepath.SkipDir
				} else {
					if *flag_verbose {
						log.Printf("Watching directory '%s' for changes.\n", path)
					}
					return watcher.Add(path)
				}
			}
			return err
		})

		if err != nil {
			log.Fatal("filepath.Walk():", err)
		}

		if err := watcher.Add(*flag_directory); err != nil {
			log.Fatal("watcher.Add():", err)
		}

	} else {
		if err := watcher.Add(*flag_directory); err != nil {
			log.Fatal("watcher.Add():", err)
		}
	}

	pattern := regexp.MustCompile(*flag_pattern)
	jobs := make(chan string)
	buildSuccess := make(chan bool)
	buildStarted := make(chan string)

	go builder(jobs, buildStarted, buildSuccess)

	if *flag_command != "" {
		go runner(*flag_command, buildStarted, buildSuccess)
	} else {
		go flusher(buildStarted, buildSuccess)
	}

	for {
		select {
		case ev := <-watcher.Events:
			if ev.Op&fsnotify.Remove == fsnotify.Remove || ev.Op&fsnotify.Write == fsnotify.Write || ev.Op&fsnotify.Create == fsnotify.Create {
				base := filepath.Base(ev.Name)

				// Assume it is a directory and track it.
				if *flag_recursive == true && !flag_excludedDirs.Matches(ev.Name) {
					watcher.Add(ev.Name)
				}

				if flag_includedFiles.Matches(base) || matchesPattern(pattern, ev.Name) {
					if !flag_excludedFiles.Matches(base) {
						jobs <- ev.Name
					}
				}
			}

		case err := <-watcher.Errors:
			if v, ok := err.(*os.SyscallError); ok {
				if v.Err == syscall.EINTR {
					continue
				}
				log.Fatal("watcher.Error: SyscallError:", v)
			}
			log.Fatal("watcher.Error:", err)
		}
	}
}
<|endoftext|>


<commit_before>package gogo

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

const (
	FS_ZERO = iota
	FS_PAT33
	FS_EDGE_DIS
	FS_EDGE_DIS_X
	FS_EDGE_DIS_Y
)

func (b *Board) PointSimpleFeature(p Point, stone Color) []int64 {
	if !p.Valid() {
		return nil
	}
	fh := b.FeatureHash(MakePoint(p.x, p.y, stone))
	if fh < 0 {
		return nil
	}
	ph := b.GetPatternHash(b.index(p.x, p.y))
	for i := 0; i < len(ph); i++ {
		ph[i] ^= fh
	}
	return ph
}

func (b *Board) GenSimpleFeatures(lastPat []int64, cur Point) map[int][]int64 {
	ret := make(map[int][]int64)
	for i, p := range b.w {
		if p.color != GRAY {
			continue
		}
		if (p.x == cur.x && p.y == cur.y) || rand.Float64() < 0.03 {
			pat := b.PointSimpleFeature(p, cur.color)
			if pat == nil {
				continue
			}
			pat = append(pat, lastPat...)
			ret[i] = pat
		}
	}
	return ret
}

func NewBoardFromPath(size int, path []*GameTreeNode) *Board {
	ret := NewBoard(size)
	for i := len(path) - 1; i >= 0; i-- {
		v := path[i]
		ret.Put(v.x, v.y, v.stone)
	}
	return ret
}

func (b *Board) SelfBattle(lastMove Point, color Color) Color {
	rand.Seed(time.Now().UnixNano())

	for {
		pass := 0
		lastMove = b.GenQuickMove(lastMove, color)
		if !lastMove.Valid() {
			pass += 1
		}

		lastMove = b.GenQuickMove(lastMove, OppColor(color))
		if !lastMove.Valid() {
			pass += 1
		}
		if pass >= 2 {
			break
		}
	}
	score := b.Score()
	if score > 0 {
		return BLACK
	} else {
		return WHITE
	}
}

func (p *GameTreeNode) BackPropVisit(root *GameTreeNode) {
	b := p
	for {
		if b == nil {
			break
		}
		b.visit += 1
		if b == root {
			break
		}
		b = b.Father
	}
}

func (p *GameTreeNode) BackPropWin(root *GameTreeNode) {
	b := p
	for {
		if b == nil {
			break
		}
		b.win += 1
		if b == root {
			break
		}
		b = b.Father
	}
}

func (g *Game) MCTreePolicy() *GameTreeNode {
	root := g.GT.Current
	node := root
	for {
		if len(node.Children) > 0 {
			maxScore := 0.0
			for _, child := range node.Children {
				score := float64(child.win) / float64(child.visit)
				score += math.Sqrt(2.0 * math.Log(float64(node.visit)) / float64(child.visit))
				if maxScore < score {
					maxScore = score
					node = child
				}
			}
		} else {
			return node
		}
	}
	return nil
}

type SingleBattleResult struct {
	node     *GameTreeNode
	winColor Color
}

func (g *Game) singleSimulate(newBoard *Board, gn *GameTreeNode, pm Point, ch chan SingleBattleResult) {
	winColor := newBoard.Copy().SelfBattle(pm, OppColor(pm.color))
	ch <- SingleBattleResult{gn, winColor}
}

func (g *Game) MCTSMove(stone Color) {
	root := g.GT.Current
	for root.visit < 1000 {
		fmt.Println(root.visit)
		node := g.MCTreePolicy()
		board := NewBoardFromPath(g.B.size, node.Path2Root())
		//info := board.CollectBoardInfo(InvalidPoint())
		cand := board.QuickCandidateMoves(Point{node.x, node.y, node.stone}, OppColor(node.stone), 20)
		//cand := info.CandidateMoves(Point{node.x, node.y, node.stone}, OppColor(node.stone), g.B.Model(), 5)
		ch := make(chan SingleBattleResult, len(cand)+1)
		n := 0
		for m, v := range cand {
			pm := board.w[m]
			if node == root {
				fmt.Printf("%s%d[%f] ", string(LX[pm.x]), pm.y+1, v)
			}
			pm.color = OppColor(node.stone)
			newBoard := board.Copy()
			if err := newBoard.Put(pm.x, pm.y, pm.color); err != nil {
				continue
			}
			gn := NewGameTreeNode(pm.color, pm.x, pm.y)
			node.AddChild(gn)
			for i := 0; i < 5; i++ {
				n += 1
				go g.singleSimulate(newBoard, gn, pm, ch)
			}
		}
		for i := 0; i < n; i++ {
			sbr := <-ch
			if sbr.winColor == stone {
				sbr.node.BackPropWin(root)
			}
			sbr.node.BackPropVisit(root)
		}
		close(ch)
		if node == root {
			fmt.Println()
		}
		if len(node.Children) == 0 {
			break
		}
	}

	maxRate := 0.0
	bestMove := root.Children[0]

	for _, child := range root.Children {
		winrate := float64(child.win) / float64(child.visit)
		fmt.Println(string(LX[child.x]), child.y+1, child.win, child.visit, winrate)
		if winrate > maxRate {
			maxRate = winrate
			bestMove = child
		}
	}

	root.Children = []*GameTreeNode{}
	g.Put(bestMove.stone, bestMove.x, bestMove.y)
}
<commit_msg>index out range<commit_after>package gogo

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

const (
	FS_ZERO = iota
	FS_PAT33
	FS_EDGE_DIS
	FS_EDGE_DIS_X
	FS_EDGE_DIS_Y
)

func (b *Board) PointSimpleFeature(p Point, stone Color) []int64 {
	if !p.Valid() {
		return nil
	}
	fh := b.FeatureHash(MakePoint(p.x, p.y, stone))
	if fh < 0 {
		return nil
	}
	ph := b.GetPatternHash(b.index(p.x, p.y))
	for i := 0; i < len(ph); i++ {
		ph[i] ^= fh
	}
	return ph
}

func (b *Board) GenSimpleFeatures(lastPat []int64, cur Point) map[int][]int64 {
	ret := make(map[int][]int64)
	pr := 3.0 / math.Max(3.0, float64(361-b.step))
	for i, p := range b.w {
		if p.color != GRAY {
			continue
		}
		if (p.x == cur.x && p.y == cur.y) || rand.Float64() < pr {
			pat := b.PointSimpleFeature(p, cur.color)
			if pat == nil {
				continue
			}
			pat = append(pat, lastPat...)
			ret[i] = pat
		}
	}
	return ret
}

func NewBoardFromPath(size int, path []*GameTreeNode) *Board {
	ret := NewBoard(size)
	for i := len(path) - 1; i >= 0; i-- {
		v := path[i]
		ret.Put(v.x, v.y, v.stone)
	}
	return ret
}

func (b *Board) SelfBattle(lastMove Point, color Color) Color {
	rand.Seed(time.Now().UnixNano())

	for {
		pass := 0
		lastMove = b.GenQuickMove(lastMove, color)
		if !lastMove.Valid() {
			pass += 1
		}

		lastMove = b.GenQuickMove(lastMove, OppColor(color))
		if !lastMove.Valid() {
			pass += 1
		}
		if pass >= 2 {
			break
		}
	}
	score := b.Score()
	if score > 0 {
		return BLACK
	} else {
		return WHITE
	}
}

func (p *GameTreeNode) BackPropVisit(root *GameTreeNode) {
	b := p
	for {
		if b == nil {
			break
		}
		b.visit += 1
		if b == root {
			break
		}
		b = b.Father
	}
}

func (p *GameTreeNode) BackPropWin(root *GameTreeNode) {
	b := p
	for {
		if b == nil {
			break
		}
		b.win += 1
		if b == root {
			break
		}
		b = b.Father
	}
}

func (g *Game) MCTreePolicy() *GameTreeNode {
	root := g.GT.Current
	node := root
	for {
		if len(node.Children) > 0 {
			maxScore := 0.0
			for _, child := range node.Children {
				score := float64(child.win) / float64(child.visit)
				score += math.Sqrt(2.0 * math.Log(float64(node.visit)) / float64(child.visit))
				if maxScore < score {
					maxScore = score
					node = child
				}
			}
		} else {
			return node
		}
	}
	return nil
}

type SingleBattleResult struct {
	node     *GameTreeNode
	winColor Color
}

func (g *Game) singleSimulate(newBoard *Board, gn *GameTreeNode, pm Point, ch chan SingleBattleResult) {
	winColor := newBoard.Copy().SelfBattle(pm, OppColor(pm.color))
	ch <- SingleBattleResult{gn, winColor}
}

func (g *Game) MCTSMove(stone Color) {
	root := g.GT.Current
	for root.visit < 1000 {
		fmt.Println(root.visit)
		node := g.MCTreePolicy()
		board := NewBoardFromPath(g.B.size, node.Path2Root())
		//info := board.CollectBoardInfo(InvalidPoint())
		cand := board.QuickCandidateMoves(Point{node.x, node.y, node.stone}, OppColor(node.stone), 20)
		//cand := info.CandidateMoves(Point{node.x, node.y, node.stone}, OppColor(node.stone), g.B.Model(), 5)
		ch := make(chan SingleBattleResult, len(cand)+1)
		n := 0
		for m, v := range cand {
			pm := board.w[m]
			if node == root {
				fmt.Printf("%s%d[%f] ", string(LX[pm.x]), pm.y+1, v)
			}
			pm.color = OppColor(node.stone)
			newBoard := board.Copy()
			if err := newBoard.Put(pm.x, pm.y, pm.color); err != nil {
				continue
			}
			gn := NewGameTreeNode(pm.color, pm.x, pm.y)
			node.AddChild(gn)
			for i := 0; i < 5; i++ {
				n += 1
				go g.singleSimulate(newBoard, gn, pm, ch)
			}
		}
		for i := 0; i < n; i++ {
			sbr := <-ch
			if sbr.winColor == stone {
				sbr.node.BackPropWin(root)
			}
			sbr.node.BackPropVisit(root)
		}
		close(ch)
		if node == root {
			fmt.Println()
		}
		if len(node.Children) == 0 {
			break
		}
	}

	maxRate := 0.0
	bestMove := root.Children[0]

	for _, child := range root.Children {
		winrate := float64(child.win) / float64(child.visit)
		fmt.Println(string(LX[child.x]), child.y+1, child.win, child.visit, winrate)
		if winrate > maxRate {
			maxRate = winrate
			bestMove = child
		}
	}

	root.Children = []*GameTreeNode{}
	g.Put(bestMove.stone, bestMove.x, bestMove.y)
}
<|endoftext|>


<commit_before>package traytor

import (
	"fmt"
	"math"
	"os"
)

const (
	MaxTreeDepth = 10
)

// Vertex is a single vertex in a mesh
type Vertex struct {
	Normal      Vec3 `json:"normal"`
	Coordinates Vec3 `json:"coordinates"`
	UV          Vec3 `json:"uv"`
}

// Triangle is a face with 3 vertices (indices in the vertex array)
type Triangle struct {
	Vertices      [3]int `json:"vertices"`
	Material      int    `json:"material"`
	AB, AC, ABxAC *Vec3
	Normal        *Vec3 `json:"normal"`
	surfaceOx     *Vec3
	surfaceOy     *Vec3
}

// Mesh is a triangle mesh
type Mesh struct {
	Vertices    []Vertex   `json:"vertices"`
	Faces       []Triangle `json:"faces"`
	tree        *KDtree
	BoundingBox *BoundingBox
}

func (m *Mesh) Init() {
	allIndices := make([]int, len(m.Faces))
	for i := range allIndices {
		allIndices[i] = i
	}

	m.BoundingBox = m.GetBoundingBox()
	m.tree = m.newKDtree(m.BoundingBox, allIndices, 0)

	for i := range m.Faces {
		triangle := &m.Faces[i]

		A := &m.Vertices[triangle.Vertices[0]].Coordinates
		B := &m.Vertices[triangle.Vertices[1]].Coordinates
		C := &m.Vertices[triangle.Vertices[2]].Coordinates

		AB := MinusVectors(B, A)
		AC := MinusVectors(C, A)

		triangle.AB = AB
		triangle.AC = AC
		triangle.ABxAC = CrossProduct(AB, AC)

		surfaceA := &m.Vertices[triangle.Vertices[0]].UV
		surfaceB := &m.Vertices[triangle.Vertices[1]].UV
		surfaceC := &m.Vertices[triangle.Vertices[2]].UV

		surfaceAB := MinusVectors(surfaceB, surfaceA)
		surfaceAC := MinusVectors(surfaceC, surfaceA)

		px, qx := SolveEquation(surfaceAB, surfaceAC, NewVec3(1, 0, 0))
		py, qy := SolveEquation(surfaceAB, surfaceAC, NewVec3(0, 1, 0))

		triangle.surfaceOx = AddVectors(AB.Scaled(px), AC.Scaled(qx))
		triangle.surfaceOy = AddVectors(AB.Scaled(py), AC.Scaled(qy))
	}
}

// Intersect finds the intersection between a ray and the mesh
// and returns their intersection and the surface material.
// Returns nil and -1 if they don't intersect
func (m *Mesh) SlowIntersect(ray *Ray) (*Intersection, int) {
	intersection := &Intersection{}
	intersection.Distance = Inf
	material := -1
	for _, triangle := range m.Faces {
		if m.intersectTriangle(ray, &triangle, intersection) {
			material = triangle.Material
		}
	}
	if material == -1 {
		return nil, -1
	}
	return intersection, material
}

func (m *Mesh) Intersect(ray *Ray) *Intersection {
	fmt.Fprintf(os.Stderr, "bbox: %v\n", m.BoundingBox)
	fmt.Fprintf(os.Stderr, "ray: %s\n", ray)

	if !m.BoundingBox.Intersect(ray) {
		return nil
	}
	fmt.Fprintf(os.Stderr, "Tree: %v\n", m.tree)
	intersectionInfo := &Intersection{Distance: Inf}
	if m.IntersectKD(ray, m.BoundingBox, m.tree, intersectionInfo) {
		return intersectionInfo
	}
	return nil
}

//Intersect triangle looks very similar to mesh.IntersectTriangle
func IntersectTriangle(ray *Ray, A, B, C *Vec3) (bool, float64) {
	AB := MinusVectors(B, A)
	AC := MinusVectors(C, A)
	reverseDirection := ray.Direction.Negative()
	distToA := MinusVectors(&ray.Start, A)
	ABxAC := CrossProduct(AB, AC)
	det := DotProduct(ABxAC, reverseDirection)
	reverseDet := 1 / det
	if math.Abs(det) < Epsilon {
		return false, Inf
	}
	lambda2 := MixedProduct(distToA, AC, reverseDirection) * reverseDet
	lambda3 := MixedProduct(AB, distToA, reverseDirection) * reverseDet
	gamma := DotProduct(ABxAC, distToA) * reverseDet
	if gamma < 0 {
		return false, Inf
	}
	if lambda2 < 0 || lambda2 > 1 || lambda3 < 0 || lambda3 > 1 || lambda2+lambda3 > 1 {
		return false, Inf
	}
	return true, gamma
}

func (m *Mesh) intersectTriangle(ray *Ray, triangle *Triangle, intersection *Intersection) bool {
	//lambda2(B - A) + lambda3(C - A) - intersectDist*rayDir = distToA
	//If the triangle is ABC, this gives you A
	A := &m.Vertices[triangle.Vertices[0]].Coordinates
	distToA := MinusVectors(&ray.Start, A)
	rayDir := ray.Direction
	ABxAC := triangle.ABxAC
	//We will find the barycentric coordinates using Cramer's formula, so we'll need the determinant
	//det is (AB^AC)*dir of the ray, but we're gonna use 1/det, so we find the recerse:
	det := -DotProduct(ABxAC, &rayDir)
	if math.Abs(det) < Epsilon {
		return false
	}
	reverseDet := 1 / det
	intersectDist := DotProduct(ABxAC, distToA) * reverseDet
	if intersectDist < 0 || intersectDist > intersection.Distance {
		return false
	}
	//lambda2 = (dist^dir)*AC / det
	//lambda3 = -(dist^dir)*AB / det
	lambda2 := MixedProduct(distToA, &rayDir, triangle.AC) * reverseDet
	lambda3 := -MixedProduct(distToA, &rayDir, triangle.AB) * reverseDet
	if lambda2 < 0 || lambda2 > 1 || lambda3 < 0 || lambda3 > 1 || lambda2+lambda3 > 1 {
		return false
	}

	intersection.Distance = intersectDist
	intersection.Point = AddVectors(&ray.Start, (&rayDir).Scaled(intersectDist))
	if triangle.Normal != nil {
		intersection.Normal = triangle.Normal
	} else {
		Anormal := &m.Vertices[triangle.Vertices[0]].Normal
		Bnormal := &m.Vertices[triangle.Vertices[1]].Normal
		Cnormal := &m.Vertices[triangle.Vertices[2]].Normal
		ABxlambda2 := MinusVectors(Bnormal, Anormal).Scaled(lambda2)
		ACxlambda3 := MinusVectors(Cnormal, Anormal).Scaled(lambda3)
		intersection.Normal = AddVectors(Anormal, AddVectors(ABxlambda2, ACxlambda3))
	}
	uvA := &m.Vertices[triangle.Vertices[0]].UV
	uvB := &m.Vertices[triangle.Vertices[1]].UV
	uvC := &m.Vertices[triangle.Vertices[2]].UV

	uvABxlambda2 := MinusVectors(uvB, uvA).Scaled(lambda2)
	uvACxlambda3 := MinusVectors(uvC, uvA).Scaled(lambda3)
	uv := AddVectors(uvA, AddVectors(uvABxlambda2, uvACxlambda3))

	intersection.U = uv.X
	intersection.V = uv.Y

	intersection.SurfaceOx = triangle.surfaceOx
	intersection.SurfaceOy = triangle.surfaceOy

	intersection.Incoming = ray
	return true
}

func (m *Mesh) GetBoundingBox() *BoundingBox {
	boundingBox := NewBoundingBox()
	for _, vertex := range m.Vertices {
		boundingBox.AddPoint(&vertex.Coordinates)
	}
	return boundingBox
}

func (m *Mesh) newKDtree(boundingBox *BoundingBox, trianglesIndices []int, depth int) *KDtree {
	if depth > MaxTreeDepth || len(trianglesIndices) < 2 {
		node := NewLeaf(trianglesIndices)
		return node
	}
	axis := depth % 3
	leftLimit := boundingBox.MaxVolume.GetDimension(axis)
	righLimit := boundingBox.MinVolume.GetDimension(axis)

	median := (leftLimit + righLimit) / 2

	var leftTriangles, rightTriangles []int
	var A, B, C *Vec3
	leftBoundingBox, rightBoundingBox := boundingBox.Split(axis, median)
	for _, index := range trianglesIndices {
		A = &m.Vertices[m.Faces[index].Vertices[0]].Coordinates
		B = &m.Vertices[m.Faces[index].Vertices[1]].Coordinates
		C = &m.Vertices[m.Faces[index].Vertices[2]].Coordinates

		if leftBoundingBox.IntersectTriangle(A, B, C) {
			leftTriangles = append(leftTriangles, index)
		}

		if rightBoundingBox.IntersectTriangle(A, B, C) {
			rightTriangles = append(rightTriangles, index)
		}
	}
	node := NewNode(median, axis)
	leftChild := m.newKDtree(leftBoundingBox, leftTriangles, depth+1)
	rightChild := m.newKDtree(rightBoundingBox, rightTriangles, depth+1)
	node.Children[0] = leftChild
	node.Children[1] = rightChild
	return node
}

func (m *Mesh) IntersectKD(ray *Ray, boundingBox *BoundingBox, node *KDtree, intersectionInfo *Intersection) bool {
	foundIntersection := false
	if node.Axis == Leaf {
		for _, triangle := range node.Triangles {
			if m.intersectTriangle(ray, &m.Faces[triangle], intersectionInfo) {
				foundIntersection = true
			}
		}
		return foundIntersection
	}

	leftBoundingBoxChild, rightBoundingBoxChild := boundingBox.Split(node.Axis, node.Median)
	var firstBoundingBox, secondBoundingBox *BoundingBox
	var firstNodeChild, secondNodeChild *KDtree
	if ray.Start.GetDimension(node.Axis) > node.Median {
		firstBoundingBox = leftBoundingBoxChild
		secondBoundingBox = rightBoundingBoxChild
		firstNodeChild = node.Children[0]
		secondNodeChild = node.Children[1]
	} else {
		firstBoundingBox = leftBoundingBoxChild
		secondBoundingBox = rightBoundingBoxChild
		firstNodeChild = node.Children[1]
		secondNodeChild = node.Children[0]
	}

	if boundingBox.IntersectWall(node.Axis, node.Median, ray) {
		if m.IntersectKD(ray, firstBoundingBox, firstNodeChild, intersectionInfo) {
			return m.IntersectKD(ray, secondBoundingBox, secondNodeChild, intersectionInfo)
		}
	} else {
		if firstBoundingBox.Intersect(ray) {
			return m.IntersectKD(ray, firstBoundingBox, firstNodeChild, intersectionInfo)
		} else {
			return m.IntersectKD(ray, secondBoundingBox, secondNodeChild, intersectionInfo)
		}
	}
	return false
}
<commit_msg>Intersect Triangle puts the material in the intersection info<commit_after>package traytor

import (
	"math"
)

const (
	MaxTreeDepth = 10
)

// Vertex is a single vertex in a mesh
type Vertex struct {
	Normal      Vec3 `json:"normal"`
	Coordinates Vec3 `json:"coordinates"`
	UV          Vec3 `json:"uv"`
}

// Triangle is a face with 3 vertices (indices in the vertex array)
type Triangle struct {
	Vertices      [3]int `json:"vertices"`
	Material      int    `json:"material"`
	AB, AC, ABxAC *Vec3
	Normal        *Vec3 `json:"normal"`
	surfaceOx     *Vec3
	surfaceOy     *Vec3
}

// Mesh is a triangle mesh
type Mesh struct {
	Vertices    []Vertex   `json:"vertices"`
	Faces       []Triangle `json:"faces"`
	tree        *KDtree
	BoundingBox *BoundingBox
}

func (m *Mesh) Init() {
	allIndices := make([]int, len(m.Faces))
	for i := range allIndices {
		allIndices[i] = i
	}

	m.BoundingBox = m.GetBoundingBox()
	m.tree = m.newKDtree(m.BoundingBox, allIndices, 0)

	for i := range m.Faces {
		triangle := &m.Faces[i]

		A := &m.Vertices[triangle.Vertices[0]].Coordinates
		B := &m.Vertices[triangle.Vertices[1]].Coordinates
		C := &m.Vertices[triangle.Vertices[2]].Coordinates

		AB := MinusVectors(B, A)
		AC := MinusVectors(C, A)

		triangle.AB = AB
		triangle.AC = AC
		triangle.ABxAC = CrossProduct(AB, AC)

		surfaceA := &m.Vertices[triangle.Vertices[0]].UV
		surfaceB := &m.Vertices[triangle.Vertices[1]].UV
		surfaceC := &m.Vertices[triangle.Vertices[2]].UV

		surfaceAB := MinusVectors(surfaceB, surfaceA)
		surfaceAC := MinusVectors(surfaceC, surfaceA)

		px, qx := SolveEquation(surfaceAB, surfaceAC, NewVec3(1, 0, 0))
		py, qy := SolveEquation(surfaceAB, surfaceAC, NewVec3(0, 1, 0))

		triangle.surfaceOx = AddVectors(AB.Scaled(px), AC.Scaled(qx))
		triangle.surfaceOy = AddVectors(AB.Scaled(py), AC.Scaled(qy))
	}
}

// Intersect finds the intersection between a ray and the mesh
// and returns their intersection and the surface material.
// Returns nil and -1 if they don't intersect
func (m *Mesh) SlowIntersect(ray *Ray) (*Intersection, int) {
	intersection := &Intersection{}
	intersection.Distance = Inf
	material := -1
	for _, triangle := range m.Faces {
		if m.intersectTriangle(ray, &triangle, intersection) {
			material = triangle.Material
		}
	}
	if material == -1 {
		return nil, -1
	}
	return intersection, material
}

func (m *Mesh) Intersect(ray *Ray) *Intersection {
	if !m.BoundingBox.Intersect(ray) {
		return nil
	}
	intersectionInfo := &Intersection{Distance: Inf}
	if m.IntersectKD(ray, m.BoundingBox, m.tree, intersectionInfo) {
		return intersectionInfo
	}
	return nil
}

//Intersect triangle looks very similar to mesh.IntersectTriangle
func IntersectTriangle(ray *Ray, A, B, C *Vec3) (bool, float64) {
	AB := MinusVectors(B, A)
	AC := MinusVectors(C, A)
	reverseDirection := ray.Direction.Negative()
	distToA := MinusVectors(&ray.Start, A)
	ABxAC := CrossProduct(AB, AC)
	det := DotProduct(ABxAC, reverseDirection)
	reverseDet := 1 / det
	if math.Abs(det) < Epsilon {
		return false, Inf
	}
	lambda2 := MixedProduct(distToA, AC, reverseDirection) * reverseDet
	lambda3 := MixedProduct(AB, distToA, reverseDirection) * reverseDet
	gamma := DotProduct(ABxAC, distToA) * reverseDet
	if gamma < 0 {
		return false, Inf
	}
	if lambda2 < 0 || lambda2 > 1 || lambda3 < 0 || lambda3 > 1 || lambda2+lambda3 > 1 {
		return false, Inf
	}
	return true, gamma
}

func (m *Mesh) intersectTriangle(ray *Ray, triangle *Triangle, intersection *Intersection) bool {
	//lambda2(B - A) + lambda3(C - A) - intersectDist*rayDir = distToA
	//If the triangle is ABC, this gives you A
	A := &m.Vertices[triangle.Vertices[0]].Coordinates
	distToA := MinusVectors(&ray.Start, A)
	rayDir := ray.Direction
	ABxAC := triangle.ABxAC
	//We will find the barycentric coordinates using Cramer's formula, so we'll need the determinant
	//det is (AB^AC)*dir of the ray, but we're gonna use 1/det, so we find the recerse:
	det := -DotProduct(ABxAC, &rayDir)
	if math.Abs(det) < Epsilon {
		return false
	}
	reverseDet := 1 / det
	intersectDist := DotProduct(ABxAC, distToA) * reverseDet
	if intersectDist < 0 || intersectDist > intersection.Distance {
		return false
	}
	//lambda2 = (dist^dir)*AC / det
	//lambda3 = -(dist^dir)*AB / det
	lambda2 := MixedProduct(distToA, &rayDir, triangle.AC) * reverseDet
	lambda3 := -MixedProduct(distToA, &rayDir, triangle.AB) * reverseDet
	if lambda2 < 0 || lambda2 > 1 || lambda3 < 0 || lambda3 > 1 || lambda2+lambda3 > 1 {
		return false
	}

	intersection.Distance = intersectDist
	intersection.Point = AddVectors(&ray.Start, (&rayDir).Scaled(intersectDist))
	if triangle.Normal != nil {
		intersection.Normal = triangle.Normal
	} else {
		Anormal := &m.Vertices[triangle.Vertices[0]].Normal
		Bnormal := &m.Vertices[triangle.Vertices[1]].Normal
		Cnormal := &m.Vertices[triangle.Vertices[2]].Normal
		ABxlambda2 := MinusVectors(Bnormal, Anormal).Scaled(lambda2)
		ACxlambda3 := MinusVectors(Cnormal, Anormal).Scaled(lambda3)
		intersection.Normal = AddVectors(Anormal, AddVectors(ABxlambda2, ACxlambda3))
	}
	uvA := &m.Vertices[triangle.Vertices[0]].UV
	uvB := &m.Vertices[triangle.Vertices[1]].UV
	uvC := &m.Vertices[triangle.Vertices[2]].UV

	uvABxlambda2 := MinusVectors(uvB, uvA).Scaled(lambda2)
	uvACxlambda3 := MinusVectors(uvC, uvA).Scaled(lambda3)
	uv := AddVectors(uvA, AddVectors(uvABxlambda2, uvACxlambda3))

	intersection.U = uv.X
	intersection.V = uv.Y

	intersection.SurfaceOx = triangle.surfaceOx
	intersection.SurfaceOy = triangle.surfaceOy

	intersection.Incoming = ray
	intersection.Material = triangle.Material
	return true
}

func (m *Mesh) GetBoundingBox() *BoundingBox {
	boundingBox := NewBoundingBox()
	for _, vertex := range m.Vertices {
		boundingBox.AddPoint(&vertex.Coordinates)
	}
	return boundingBox
}

func (m *Mesh) newKDtree(boundingBox *BoundingBox, trianglesIndices []int, depth int) *KDtree {
	if depth > MaxTreeDepth || len(trianglesIndices) < 2 {
		node := NewLeaf(trianglesIndices)
		return node
	}
	axis := depth % 3
	leftLimit := boundingBox.MaxVolume.GetDimension(axis)
	righLimit := boundingBox.MinVolume.GetDimension(axis)

	median := (leftLimit + righLimit) / 2

	var leftTriangles, rightTriangles []int
	var A, B, C *Vec3
	leftBoundingBox, rightBoundingBox := boundingBox.Split(axis, median)
	for _, index := range trianglesIndices {
		A = &m.Vertices[m.Faces[index].Vertices[0]].Coordinates
		B = &m.Vertices[m.Faces[index].Vertices[1]].Coordinates
		C = &m.Vertices[m.Faces[index].Vertices[2]].Coordinates

		if leftBoundingBox.IntersectTriangle(A, B, C) {
			leftTriangles = append(leftTriangles, index)
		}

		if rightBoundingBox.IntersectTriangle(A, B, C) {
			rightTriangles = append(rightTriangles, index)
		}
	}
	node := NewNode(median, axis)
	leftChild := m.newKDtree(leftBoundingBox, leftTriangles, depth+1)
	rightChild := m.newKDtree(rightBoundingBox, rightTriangles, depth+1)
	node.Children[0] = leftChild
	node.Children[1] = rightChild
	return node
}

func (m *Mesh) IntersectKD(ray *Ray, boundingBox *BoundingBox, node *KDtree, intersectionInfo *Intersection) bool {
	foundIntersection := false
	if node.Axis == Leaf {
		for _, triangle := range node.Triangles {
			if m.intersectTriangle(ray, &m.Faces[triangle], intersectionInfo) {
				foundIntersection = true
			}
		}
		return foundIntersection
	}

	leftBoundingBoxChild, rightBoundingBoxChild := boundingBox.Split(node.Axis, node.Median)
	var firstBoundingBox, secondBoundingBox *BoundingBox
	var firstNodeChild, secondNodeChild *KDtree
	if ray.Start.GetDimension(node.Axis) > node.Median {
		firstBoundingBox = leftBoundingBoxChild
		secondBoundingBox = rightBoundingBoxChild
		firstNodeChild = node.Children[0]
		secondNodeChild = node.Children[1]
	} else {
		firstBoundingBox = leftBoundingBoxChild
		secondBoundingBox = rightBoundingBoxChild
		firstNodeChild = node.Children[1]
		secondNodeChild = node.Children[0]
	}

	if boundingBox.IntersectWall(node.Axis, node.Median, ray) {
		if m.IntersectKD(ray, firstBoundingBox, firstNodeChild, intersectionInfo) {
			return m.IntersectKD(ray, secondBoundingBox, secondNodeChild, intersectionInfo)
		}
	} else {
		if firstBoundingBox.Intersect(ray) {
			return m.IntersectKD(ray, firstBoundingBox, firstNodeChild, intersectionInfo)
		} else {
			return m.IntersectKD(ray, secondBoundingBox, secondNodeChild, intersectionInfo)
		}
	}
	return false
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"sync"
	"time"

	"github.com/BurntSushi/toml"
	"github.com/Sirupsen/logrus"
	"github.com/gorilla/mux"
	"github.com/peterbourgon/g2s"
)

type Task struct {
	Host         string
	Ports        []int64
	ServicePorts []int64
	StagedAt     string
	StartedAt    string
	Version      string
}

type PortDefinitions struct {
	Port     int64
	Protocol string
	Labels   map[string]string
}

type App struct {
	Tasks           []Task
	Labels          map[string]string
	Env             map[string]string
	Hosts           []string
	PortDefinitions []PortDefinitions
}

type Config struct {
	sync.RWMutex
	Xproxy         string
	Port           string   `json:"-"`
	Marathon       []string `json:"-"`
	User           string   `json:"-"`
	Pass           string   `json:"-"`
	Nginx_config   string   `json:"-"`
	Nginx_template string   `json:"-"`
	Nginx_cmd      string   `json:"-"`
	Statsd         StatsdConfig
	LastUpdates    Updates
	Apps           map[string]App
}

type Updates struct {
	LastSync           time.Time
	LastConfigRendered time.Time
	LastConfigValid    time.Time
	LastNginxReload    time.Time
}

type StatsdConfig struct {
	Addr       string
	Namespace  string
	SampleRate int `toml:"sample_rate"`
}

type Status struct {
	Healthy bool
	Message string
}

type EndpointStatus struct {
	Endpoint string
	Healthy  bool
	Message  string
}

type Health struct {
	Config    Status
	Template  Status
	Endpoints []EndpointStatus
}

// Global variables
var VERSION string //added by goxc
var config Config
var statsd g2s.Statter
var health Health
var logger = logrus.New()

// Eventqueue with buffer of two, because we dont really need more.
var eventqueue = make(chan bool, 2)

// Global http transport for connection reuse
var tr = &http.Transport{MaxIdleConnsPerHost: 10}

func newHealth() Health {
	var h Health
	for _, ep := range config.Marathon {
		var s EndpointStatus
		s.Endpoint = ep
		s.Healthy = true
		s.Message = "OK"
		h.Endpoints = append(h.Endpoints, s)
	}
	return h
}

func nixy_reload(w http.ResponseWriter, r *http.Request) {
	logger.WithFields(logrus.Fields{
		"client": r.RemoteAddr,
	}).Info("marathon reload triggered")
	select {
	case eventqueue <- true: // Add reload to our queue channel, unless it is full of course.
		w.WriteHeader(202)
		fmt.Fprintln(w, "queued")
		return
	default:
		w.WriteHeader(202)
		fmt.Fprintln(w, "queue is full")
		return
	}
}

func nixy_health(w http.ResponseWriter, r *http.Request) {
	err := checkTmpl()
	if err != nil {
		health.Template.Message = err.Error()
		health.Template.Healthy = false
		w.WriteHeader(http.StatusInternalServerError)
	} else {
		health.Template.Message = "OK"
		health.Template.Healthy = true
	}
	err = checkConf(config.Nginx_config)
	if err != nil {
		health.Config.Message = err.Error()
		health.Config.Healthy = false
		w.WriteHeader(http.StatusInternalServerError)
	} else {
		health.Config.Message = "OK"
		health.Config.Healthy = true
	}
	for _, endpoint := range health.Endpoints {
		if (endpoint.Healthy) {
			break;
		}
		w.WriteHeader(http.StatusInternalServerError)
	}
	w.Header().Add("Content-Type", "application/json; charset=utf-8")
	b, _ := json.MarshalIndent(health, "", "  ")
	w.Write(b)
	return
}

func nixy_config(w http.ResponseWriter, r *http.Request) {
	w.Header().Add("Content-Type", "application/json; charset=utf-8")
	b, _ := json.MarshalIndent(config, "", "  ")
	w.Write(b)
	return
}

func nixy_version(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "nixy "+VERSION)
	return
}

func main() {
	configtoml := flag.String("f", "nixy.toml", "Path to config. (default nixy.toml)")
	version := flag.Bool("v", false, "prints current nixy version")
	flag.Parse()
	if *version {
		fmt.Println(VERSION)
		os.Exit(0)
	}
	file, err := ioutil.ReadFile(*configtoml)
	if err != nil {
		logger.WithFields(logrus.Fields{
			"error": err.Error(),
		}).Fatal("problem opening toml config")
	}
	err = toml.Unmarshal(file, &config)
	if err != nil {
		logger.WithFields(logrus.Fields{
			"error": err.Error(),
		}).Fatal("problem parsing config")
	}

	statsd, _ = setupStatsd()

	mux := mux.NewRouter()
	mux.HandleFunc("/", nixy_version)
	mux.HandleFunc("/v1/reload", nixy_reload)
	mux.HandleFunc("/v1/config", nixy_config)
	mux.HandleFunc("/v1/health", nixy_health)
	s := &http.Server{
		Addr:    ":" + config.Port,
		Handler: mux,
	}
	health = newHealth()
	endpointHealth()
	eventStream()
	eventWorker()
	logger.Info("starting nixy on :" + config.Port)
	err = s.ListenAndServe()
	if err != nil {
		log.Fatal(err)
	}
}
<commit_msg>fix: send 500 on health check only if all endpoints are erroneous<commit_after>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"sync"
	"time"

	"github.com/BurntSushi/toml"
	"github.com/Sirupsen/logrus"
	"github.com/gorilla/mux"
	"github.com/peterbourgon/g2s"
)

type Task struct {
	Host         string
	Ports        []int64
	ServicePorts []int64
	StagedAt     string
	StartedAt    string
	Version      string
}

type PortDefinitions struct {
	Port     int64
	Protocol string
	Labels   map[string]string
}

type App struct {
	Tasks           []Task
	Labels          map[string]string
	Env             map[string]string
	Hosts           []string
	PortDefinitions []PortDefinitions
}

type Config struct {
	sync.RWMutex
	Xproxy         string
	Port           string   `json:"-"`
	Marathon       []string `json:"-"`
	User           string   `json:"-"`
	Pass           string   `json:"-"`
	Nginx_config   string   `json:"-"`
	Nginx_template string   `json:"-"`
	Nginx_cmd      string   `json:"-"`
	Statsd         StatsdConfig
	LastUpdates    Updates
	Apps           map[string]App
}

type Updates struct {
	LastSync           time.Time
	LastConfigRendered time.Time
	LastConfigValid    time.Time
	LastNginxReload    time.Time
}

type StatsdConfig struct {
	Addr       string
	Namespace  string
	SampleRate int `toml:"sample_rate"`
}

type Status struct {
	Healthy bool
	Message string
}

type EndpointStatus struct {
	Endpoint string
	Healthy  bool
	Message  string
}

type Health struct {
	Config    Status
	Template  Status
	Endpoints []EndpointStatus
}

// Global variables
var VERSION string //added by goxc
var config Config
var statsd g2s.Statter
var health Health
var logger = logrus.New()

// Eventqueue with buffer of two, because we dont really need more.
var eventqueue = make(chan bool, 2)

// Global http transport for connection reuse
var tr = &http.Transport{MaxIdleConnsPerHost: 10}

func newHealth() Health {
	var h Health
	for _, ep := range config.Marathon {
		var s EndpointStatus
		s.Endpoint = ep
		s.Healthy = true
		s.Message = "OK"
		h.Endpoints = append(h.Endpoints, s)
	}
	return h
}

func nixy_reload(w http.ResponseWriter, r *http.Request) {
	logger.WithFields(logrus.Fields{
		"client": r.RemoteAddr,
	}).Info("marathon reload triggered")
	select {
	case eventqueue <- true: // Add reload to our queue channel, unless it is full of course.
		w.WriteHeader(202)
		fmt.Fprintln(w, "queued")
		return
	default:
		w.WriteHeader(202)
		fmt.Fprintln(w, "queue is full")
		return
	}
}

func nixy_health(w http.ResponseWriter, r *http.Request) {
	err := checkTmpl()
	if err != nil {
		health.Template.Message = err.Error()
		health.Template.Healthy = false
		w.WriteHeader(http.StatusInternalServerError)
	} else {
		health.Template.Message = "OK"
		health.Template.Healthy = true
	}
	err = checkConf(config.Nginx_config)
	if err != nil {
		health.Config.Message = err.Error()
		health.Config.Healthy = false
		w.WriteHeader(http.StatusInternalServerError)
	} else {
		health.Config.Message = "OK"
		health.Config.Healthy = true
	}
	all_backends_down := true;
	for _, endpoint := range health.Endpoints {
		if (endpoint.Healthy) {
			all_backends_down = false;
			break;
		}
	}
	if all_backends_down {
		w.WriteHeader(http.StatusInternalServerError)
	}
	w.Header().Add("Content-Type", "application/json; charset=utf-8")
	b, _ := json.MarshalIndent(health, "", "  ")
	w.Write(b)
	return
}

func nixy_config(w http.ResponseWriter, r *http.Request) {
	w.Header().Add("Content-Type", "application/json; charset=utf-8")
	b, _ := json.MarshalIndent(config, "", "  ")
	w.Write(b)
	return
}

func nixy_version(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, "nixy "+VERSION)
	return
}

func main() {
	configtoml := flag.String("f", "nixy.toml", "Path to config. (default nixy.toml)")
	version := flag.Bool("v", false, "prints current nixy version")
	flag.Parse()
	if *version {
		fmt.Println(VERSION)
		os.Exit(0)
	}
	file, err := ioutil.ReadFile(*configtoml)
	if err != nil {
		logger.WithFields(logrus.Fields{
			"error": err.Error(),
		}).Fatal("problem opening toml config")
	}
	err = toml.Unmarshal(file, &config)
	if err != nil {
		logger.WithFields(logrus.Fields{
			"error": err.Error(),
		}).Fatal("problem parsing config")
	}

	statsd, _ = setupStatsd()

	mux := mux.NewRouter()
	mux.HandleFunc("/", nixy_version)
	mux.HandleFunc("/v1/reload", nixy_reload)
	mux.HandleFunc("/v1/config", nixy_config)
	mux.HandleFunc("/v1/health", nixy_health)
	s := &http.Server{
		Addr:    ":" + config.Port,
		Handler: mux,
	}
	health = newHealth()
	endpointHealth()
	eventStream()
	eventWorker()
	logger.Info("starting nixy on :" + config.Port)
	err = s.ListenAndServe()
	if err != nil {
		log.Fatal(err)
	}
}
<|endoftext|>


<commit_before>package nnmc

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"regexp"
	"strconv"
	"strings"

	"golang.org/x/net/html/charset"
)

// NNMc values:
// client http.Client with cookie
type NNMc struct {
	client http.Client
}

// Topic from forum
type Topic struct {
	Href    string
	Name    string
	Year    string
	Quality string
}

// Film all values
// ID            id
// Name          Название
// EngName       Английское название
// Href          Ссылка
// Year          Год
// Genre         Жанр
// Country       Производство
// Director      Режиссер
// Producer      Продюсер
// Actors        Актеры
// Description   Описание
// Age           Возраст
// ReleaseDate   Дата мировой премьеры
// RussianDate   Дата премьеры в России
// Duration      Продолжительность
// Quality       Качество видео
// Translation   Перевод
// SubtitlesType Вид субтитров
// Subtitles     Субтитры
// Video         Видео
// Audio         Аудио
// Kinopoisk     Рейтинг кинопоиска
// Imdb          Рейтинг IMDb
// NNM           Рейтинг nnm-club
// Sound         Звук
// Size          Размер
// DateCreate    Дата создания раздачи
// Torrent       Ссылка на torrent
// Poster        Ссылка на постер
// Hide          Скрывать в общем списке
type Film struct {
	ID            int64   `gorm:"column:id" sql:"AUTO_INCREMENT"`
	Name          string  `gorm:"column:name"`
	EngName       string  `gorm:"column:eng_name"`
	Href          string  `gorm:"column:href"`
	Year          int64   `gorm:"column:year"`
	Genre         string  `gorm:"column:genre"`
	Country       string  `gorm:"column:country"`
	Director      string  `gorm:"column:director"`
	Producer      string  `gorm:"column:producer"`
	Actors        string  `gorm:"column:actors"`
	Description   string  `gorm:"column:description"`
	Age           string  `gorm:"column:age"`
	ReleaseDate   string  `gorm:"column:release_date"`
	RussianDate   string  `gorm:"column:russian_date"`
	Duration      int64   `gorm:"column:duration"`
	Quality       string  `gorm:"column:quality"`
	Translation   string  `gorm:"column:translation"`
	SubtitlesType string  `gorm:"column:subtitles_type"`
	Subtitles     string  `gorm:"column:subtitles"`
	Video         string  `gorm:"column:video"`
	Audio         string  `gorm:"column:audio"`
	Kinopoisk     float64 `gorm:"column:kinopoisk"`
	IMDb          float64 `gorm:"column:imdb"`
	NNM           float64 `gorm:"column:nnm"`
	Sound         string  `gorm:"column:sound"`
	Size          int64   `gorm:"column:size"`
	DateCreate    string  `gorm:"column:date_create"`
	Torrent       string  `gorm:"column:torrent"`
	Poster        string  `gorm:"column:poster"`
	Hide          bool    `gorm:"column:hide" sql:"default:0"`
}

// Init nnmc with login password
func Init(login string, password string) (*NNMc, error) {
	var client http.Client
	cookieJar, _ := cookiejar.New(nil)
	client.Jar = cookieJar
	urlPost := "http://nnm-club.me/forum/login.php"
	form := url.Values{}
	form.Set("username", login)
	form.Add("password", password)
	form.Add("redirect", "")
	form.Add("login", "âõîä")
	req, _ := http.NewRequest("POST", urlPost, bytes.NewBufferString(form.Encode()))
	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
	req.Header.Add("Content-Length", strconv.Itoa(len(form.Encode())))
	_, err := client.Do(req)
	return &NNMc{client: client}, err
}

// getHTML get body from url
func getHTML(url string, n *NNMc) ([]byte, error) {
	resp, err := n.client.Get(url)
	if err != nil {
		log.Println("client Get error:", err)
		return nil, err
	}
	defer resp.Body.Close()
	utf8body, err := charset.NewReader(resp.Body, resp.Header.Get("Content-Type"))
	if err != nil {
		log.Println("Encoding error:", err)
		return nil, err
	}
	doc, err := ioutil.ReadAll(utf8body)
	if err != nil {
		log.Println("ioutil.ReadAll error:", err)
	}
	doc = replaceAll(doc, " ", " ")
	doc = replaceAll(doc, "&", "&")
	return doc, nil
}

// ParseForumTree get topics from forumTree
func (n *NNMc) ParseForumTree(url string) ([]Topic, error) {
	var (
		topics []Topic
		reTree = regexp.MustCompile(`<a href="(viewtopic.php\?t=\d+)"class="topictitle">(.+?)\s\((\d{4})\)\s(.+?)</a>`)
	)
	body, err := getHTML(url, n)
	if err != nil {
		return topics, err
	}
	if reTree.Match(body) == false {
		return topics, fmt.Errorf("No topic in body")
	}
	findResult := reTree.FindAllSubmatch(body, -1)
	for _, v := range findResult {
		var t Topic
		t.Href = "http://nnm-club.me/forum/" + string(v[1])
		t.Name = string(v[2])
		t.Year = string(v[3])
		t.Quality = string(v[4])
		topics = append(topics, t)
	}
	return topics, nil
}

// ParseTopic get film from topic
func (n *NNMc) ParseTopic(topic Topic) (Film, error) {
	var (
		film     Film
		reTopic  = regexp.MustCompile(`(Производство|Жанр|Режиссер|Продюсер|Актеры|Описание фильма|Описание|Возраст|Дата мировой премьеры|Дата премьеры в России|Дата Российской премьеры|Дата российской премьеры|Продолжительность|Качество видео|Качество|Перевод|Вид субтитров|Субтитры|Видео|Аудио):<\/span>(.+?)<`)
		reDate   = regexp.MustCompile(`> (\d{1,2} .{3} \d{4}).{9}<`)
		reSize   = regexp.MustCompile(`Размер блока: \d.+?B"> (\d{1,2},\d{1,2}|\d{3,4}|\d{1,2})\s`)
		reRating = regexp.MustCompile(`>(\d,\d|\d)<\/span>.+?\(Голосов:`)
		reDl     = regexp.MustCompile(`<a href="download\.php\?id=(\d{5,7})" rel="nofollow">Скачать<`)
		reImg    = regexp.MustCompile(`"postImg postImgAligned img-right" title="http:\/\/assets\.nnm-club\.ws\/forum\/image\.php\?link=(.+?jpg)`)
	)
	name := strings.Split(topic.Name, "/")
	switch len(name) {
	case 1:
		film.Name = strings.Trim(name[0], " ")
	case 2:
		film.Name = strings.Trim(name[0], " ")
		film.EngName = strings.Trim(name[1], " ")
	case 3:
		film.Name = strings.Trim(name[0], " ")
		film.EngName = strings.Trim(name[1], " ")
	}
	film.Href = topic.Href
	if year64, err := strconv.ParseInt(topic.Year, 10, 64); err == nil {
		film.Year = year64
	}
	body, err := getHTML(film.Href, n)
	if err != nil {
		return film, err
	}
	if reTopic.Match(body) == false {
		return film, fmt.Errorf("No topic in body")
	}
	findAttrs := reTopic.FindAllSubmatch(body, -1)
	for _, v := range findAttrs {
		one := strings.Trim(string(v[1]), " ")
		two := strings.Replace(string(v[2]), "
", "", -1)
		two = strings.Trim(two, " ")
		switch one {
		case "Производство":
			film.Country = two
		case "Жанр":
			film.Genre = strings.ToLower(two)
		case "Режиссер":
			film.Director = two
		case "Продюсер":
			film.Producer = two
		case "Актеры":
			film.Actors = two
		case "Описание фильма", "Описание":
			film.Description = two
		case "Возраст":
			film.Age = two
		case "Дата мировой премьеры":
			film.ReleaseDate = two
		case "Дата премьеры в России", "Дата российской премьеры", "Дата Российской премьеры":
			film.RussianDate = two
		case "Продолжительность":
			if i64, err := strconv.ParseInt(two, 10, 64); err == nil {
				film.Duration = i64
			}
		case "Качество видео", "Качество":
			film.Quality = two
		case "Перевод":
			film.Translation = two
		case "Вид субтитров":
			film.SubtitlesType = two
		case "Субтитры":
			film.Subtitles = two
		case "Видео":
			film.Video = two
		case "Аудио":
			film.Audio = two
		}
	}
	if reDl.Match(body) == false {
		return film, fmt.Errorf("No torrent url in body")
	}
	findDl := reDl.FindAllSubmatch(body, -1)
	film.Torrent = "http://nnm-club.me/forum/download.php?id=" + string(findDl[0][1])
	if reDate.Match(body) == true {
		film.DateCreate = replaceDate(string(reDate.FindSubmatch(body)[1]))
	}
	if reSize.Match(body) == true {
		size := string(reSize.FindSubmatch(body)[1])
		size = strings.Replace(size, ",", ".", -1)
		if s64, err := strconv.ParseFloat(size, 64); err == nil {
			if s64 < 100 {
				s64 = s64 * 1000
			}
			film.Size = int64(s64)
		}
	}
	if reRating.Match(body) == true {
		rating := string(reRating.FindSubmatch(body)[1])
		rating = strings.Replace(rating, ",", ".", -1)
		if r64, err := strconv.ParseFloat(rating, 64); err == nil {
			film.NNM = r64
		}
	}
	if reImg.Match(body) == true {
		film.Poster = string(reImg.FindSubmatch(body)[1])
	}
	return film, nil
}

func replaceAll(body []byte, from string, to string) []byte {
	var reStr = regexp.MustCompile(from)
	result := reStr.ReplaceAll(body, []byte(to))
	return result
}

func replaceDate(s string) string {
	s = strings.Replace(s, " Янв ", ".01.", -1)
	s = strings.Replace(s, " Фев ", ".02.", -1)
	s = strings.Replace(s, " Мар ", ".03.", -1)
	s = strings.Replace(s, " Апр ", ".04.", -1)
	s = strings.Replace(s, " Май ", ".05.", -1)
	s = strings.Replace(s, " Июн ", ".06.", -1)
	s = strings.Replace(s, " Июл ", ".07.", -1)
	s = strings.Replace(s, " Авг ", ".08.", -1)
	s = strings.Replace(s, " Сен ", ".09.", -1)
	s = strings.Replace(s, " Окт ", ".10.", -1)
	s = strings.Replace(s, " Ноя ", ".11.", -1)
	s = strings.Replace(s, " Дек ", ".12.", -1)
	return s
}
<commit_msg>fix regext image<commit_after>package nnmc

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"regexp"
	"strconv"
	"strings"

	"golang.org/x/net/html/charset"
)

// NNMc values:
// client http.Client with cookie
type NNMc struct {
	client http.Client
}

// Topic from forum
type Topic struct {
	Href    string
	Name    string
	Year    string
	Quality string
}

// Film all values
// ID            id
// Name          Название
// EngName       Английское название
// Href          Ссылка
// Year          Год
// Genre         Жанр
// Country       Производство
// Director      Режиссер
// Producer      Продюсер
// Actors        Актеры
// Description   Описание
// Age           Возраст
// ReleaseDate   Дата мировой премьеры
// RussianDate   Дата премьеры в России
// Duration      Продолжительность
// Quality       Качество видео
// Translation   Перевод
// SubtitlesType Вид субтитров
// Subtitles     Субтитры
// Video         Видео
// Audio         Аудио
// Kinopoisk     Рейтинг кинопоиска
// Imdb          Рейтинг IMDb
// NNM           Рейтинг nnm-club
// Sound         Звук
// Size          Размер
// DateCreate    Дата создания раздачи
// Torrent       Ссылка на torrent
// Poster        Ссылка на постер
// Hide          Скрывать в общем списке
type Film struct {
	ID            int64   `gorm:"column:id" sql:"AUTO_INCREMENT"`
	Name          string  `gorm:"column:name"`
	EngName       string  `gorm:"column:eng_name"`
	Href          string  `gorm:"column:href"`
	Year          int64   `gorm:"column:year"`
	Genre         string  `gorm:"column:genre"`
	Country       string  `gorm:"column:country"`
	Director      string  `gorm:"column:director"`
	Producer      string  `gorm:"column:producer"`
	Actors        string  `gorm:"column:actors"`
	Description   string  `gorm:"column:description"`
	Age           string  `gorm:"column:age"`
	ReleaseDate   string  `gorm:"column:release_date"`
	RussianDate   string  `gorm:"column:russian_date"`
	Duration      int64   `gorm:"column:duration"`
	Quality       string  `gorm:"column:quality"`
	Translation   string  `gorm:"column:translation"`
	SubtitlesType string  `gorm:"column:subtitles_type"`
	Subtitles     string  `gorm:"column:subtitles"`
	Video         string  `gorm:"column:video"`
	Audio         string  `gorm:"column:audio"`
	Kinopoisk     float64 `gorm:"column:kinopoisk"`
	IMDb          float64 `gorm:"column:imdb"`
	NNM           float64 `gorm:"column:nnm"`
	Sound         string  `gorm:"column:sound"`
	Size          int64   `gorm:"column:size"`
	DateCreate    string  `gorm:"column:date_create"`
	Torrent       string  `gorm:"column:torrent"`
	Poster        string  `gorm:"column:poster"`
	Hide          bool    `gorm:"column:hide" sql:"default:0"`
}

// Init nnmc with login password
func Init(login string, password string) (*NNMc, error) {
	var client http.Client
	cookieJar, _ := cookiejar.New(nil)
	client.Jar = cookieJar
	urlPost := "http://nnm-club.me/forum/login.php"
	form := url.Values{}
	form.Set("username", login)
	form.Add("password", password)
	form.Add("redirect", "")
	form.Add("login", "âõîä")
	req, _ := http.NewRequest("POST", urlPost, bytes.NewBufferString(form.Encode()))
	req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
	req.Header.Add("Content-Length", strconv.Itoa(len(form.Encode())))
	_, err := client.Do(req)
	return &NNMc{client: client}, err
}

// getHTML get body from url
func getHTML(url string, n *NNMc) ([]byte, error) {
	resp, err := n.client.Get(url)
	if err != nil {
		log.Println("client Get error:", err)
		return nil, err
	}
	defer resp.Body.Close()
	utf8body, err := charset.NewReader(resp.Body, resp.Header.Get("Content-Type"))
	if err != nil {
		log.Println("Encoding error:", err)
		return nil, err
	}
	doc, err := ioutil.ReadAll(utf8body)
	if err != nil {
		log.Println("ioutil.ReadAll error:", err)
	}
	doc = replaceAll(doc, " ", " ")
	doc = replaceAll(doc, "&", "&")
	return doc, nil
}

// ParseForumTree get topics from forumTree
func (n *NNMc) ParseForumTree(url string) ([]Topic, error) {
	var (
		topics []Topic
		reTree = regexp.MustCompile(`<a href="(viewtopic.php\?t=\d+)"class="topictitle">(.+?)\s\((\d{4})\)\s(.+?)</a>`)
	)
	body, err := getHTML(url, n)
	if err != nil {
		return topics, err
	}
	if reTree.Match(body) == false {
		return topics, fmt.Errorf("No topic in body")
	}
	findResult := reTree.FindAllSubmatch(body, -1)
	for _, v := range findResult {
		var t Topic
		t.Href = "http://nnm-club.me/forum/" + string(v[1])
		t.Name = string(v[2])
		t.Year = string(v[3])
		t.Quality = string(v[4])
		topics = append(topics, t)
	}
	return topics, nil
}

// ParseTopic get film from topic
func (n *NNMc) ParseTopic(topic Topic) (Film, error) {
	var (
		film     Film
		reTopic  = regexp.MustCompile(`(Производство|Жанр|Режиссер|Продюсер|Актеры|Описание фильма|Описание|Возраст|Дата мировой премьеры|Дата премьеры в России|Дата Российской премьеры|Дата российской премьеры|Продолжительность|Качество видео|Качество|Перевод|Вид субтитров|Субтитры|Видео|Аудио):<\/span>(.+?)<`)
		reDate   = regexp.MustCompile(`> (\d{1,2} .{3} \d{4}).{9}<`)
		reSize   = regexp.MustCompile(`Размер блока: \d.+?B"> (\d{1,2},\d{1,2}|\d{3,4}|\d{1,2})\s`)
		reRating = regexp.MustCompile(`>(\d,\d|\d)<\/span>.+?\(Голосов:`)
		reDl     = regexp.MustCompile(`<a href="download\.php\?id=(\d{5,7})" rel="nofollow">Скачать<`)
		reImg    = regexp.MustCompile(`"postImg postImgAligned img-right" title="http:\/\/assets\.nnm-club\.ws\/forum\/image\.php\?link=(.+?jpe{0,1}g)`)
	)
	name := strings.Split(topic.Name, "/")
	switch len(name) {
	case 1:
		film.Name = strings.Trim(name[0], " ")
	case 2:
		film.Name = strings.Trim(name[0], " ")
		film.EngName = strings.Trim(name[1], " ")
	case 3:
		film.Name = strings.Trim(name[0], " ")
		film.EngName = strings.Trim(name[1], " ")
	}
	film.Href = topic.Href
	if year64, err := strconv.ParseInt(topic.Year, 10, 64); err == nil {
		film.Year = year64
	}
	body, err := getHTML(film.Href, n)
	if err != nil {
		return film, err
	}
	if reTopic.Match(body) == false {
		return film, fmt.Errorf("No topic in body")
	}
	findAttrs := reTopic.FindAllSubmatch(body, -1)
	for _, v := range findAttrs {
		one := strings.Trim(string(v[1]), " ")
		two := strings.Replace(string(v[2]), "
", "", -1)
		two = strings.Trim(two, " ")
		switch one {
		case "Производство":
			film.Country = two
		case "Жанр":
			film.Genre = strings.ToLower(two)
		case "Режиссер":
			film.Director = two
		case "Продюсер":
			film.Producer = two
		case "Актеры":
			film.Actors = two
		case "Описание фильма", "Описание":
			film.Description = two
		case "Возраст":
			film.Age = two
		case "Дата мировой премьеры":
			film.ReleaseDate = two
		case "Дата премьеры в России", "Дата российской премьеры", "Дата Российской премьеры":
			film.RussianDate = two
		case "Продолжительность":
			if i64, err := strconv.ParseInt(two, 10, 64); err == nil {
				film.Duration = i64
			}
		case "Качество видео", "Качество":
			film.Quality = two
		case "Перевод":
			film.Translation = two
		case "Вид субтитров":
			film.SubtitlesType = two
		case "Субтитры":
			film.Subtitles = two
		case "Видео":
			film.Video = two
		case "Аудио":
			film.Audio = two
		}
	}
	if reDl.Match(body) == false {
		return film, fmt.Errorf("No torrent url in body")
	}
	findDl := reDl.FindAllSubmatch(body, -1)
	film.Torrent = "http://nnm-club.me/forum/download.php?id=" + string(findDl[0][1])
	if reDate.Match(body) == true {
		film.DateCreate = replaceDate(string(reDate.FindSubmatch(body)[1]))
	}
	if reSize.Match(body) == true {
		size := string(reSize.FindSubmatch(body)[1])
		size = strings.Replace(size, ",", ".", -1)
		if s64, err := strconv.ParseFloat(size, 64); err == nil {
			if s64 < 100 {
				s64 = s64 * 1000
			}
			film.Size = int64(s64)
		}
	}
	if reRating.Match(body) == true {
		rating := string(reRating.FindSubmatch(body)[1])
		rating = strings.Replace(rating, ",", ".", -1)
		if r64, err := strconv.ParseFloat(rating, 64); err == nil {
			film.NNM = r64
		}
	}
	if reImg.Match(body) == true {
		film.Poster = string(reImg.FindSubmatch(body)[1])
	}
	return film, nil
}

func replaceAll(body []byte, from string, to string) []byte {
	var reStr = regexp.MustCompile(from)
	result := reStr.ReplaceAll(body, []byte(to))
	return result
}

func replaceDate(s string) string {
	s = strings.Replace(s, " Янв ", ".01.", -1)
	s = strings.Replace(s, " Фев ", ".02.", -1)
	s = strings.Replace(s, " Мар ", ".03.", -1)
	s = strings.Replace(s, " Апр ", ".04.", -1)
	s = strings.Replace(s, " Май ", ".05.", -1)
	s = strings.Replace(s, " Июн ", ".06.", -1)
	s = strings.Replace(s, " Июл ", ".07.", -1)
	s = strings.Replace(s, " Авг ", ".08.", -1)
	s = strings.Replace(s, " Сен ", ".09.", -1)
	s = strings.Replace(s, " Окт ", ".10.", -1)
	s = strings.Replace(s, " Ноя ", ".11.", -1)
	s = strings.Replace(s, " Дек ", ".12.", -1)
	return s
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"spool-mock/config"
	"net"
	"spool-mock/client"
	"strings"
	"spool-mock/db"
	"io"
	"net/textproto"
	"bufio"
	"bytes"
	"spool-mock/dotreader"
	//"spool-mock/headreader"
	//"spool-mock/bodyreader"
)

func Quit(conn *client.Conn, tok []string) {
	conn.Send("205 Bye.")
}

func Unsupported(conn *client.Conn, tok []string) {
	fmt.Println(fmt.Sprintf("WARN: C(%s): Unsupported cmd %s", conn.RemoteAddr(), tok[0]))
	conn.Send("500 Unsupported.")
}

func read(conn *client.Conn, msgid string, msgtype string) {
	read, usrErr, sysErr := db.Read(
		db.ReadInput{Msgid: msgid[1:len(msgid)-1], Type: msgtype},
	)
	if sysErr != nil {
		fmt.Println("WARN: " + sysErr.Error())
		conn.Send("500 Failed processing")
		return
	}
	if usrErr != nil {
		conn.Send("400 " + usrErr.Error())
		return
	}
	defer read.Close()

	var code string
	if msgtype == "ARTICLE" {
		code = "220"
	} else if msgtype == "HEAD" {
		code = "221"
	} else if msgtype == "BODY" {
		code = "222"
	} else {
		panic("Should not get here")
	}

	conn.Send(code + " " + msgid)
	if _, e := io.Copy(conn.GetWriter(), read); e != nil {
		fmt.Println("WARN: " + e.Error())
		conn.Send("500 Failed forwarding")
		return
	}
	conn.Send("\r\n.") // additional \r\n auto-added
}

func PostArticle(conn *client.Conn) {
	conn.Send("340 Start posting.")

	b := new(bytes.Buffer)
	br := bufio.NewReader(conn.GetReader())
	r := textproto.NewReader(br)

	fmt.Println("PostArticle head.")
	m, e := r.ReadMIMEHeader()
	if e != nil {
		conn.Send("440 Failed reading header")
		return
	}

	fmt.Println("PostArticle body.")
	if _, e := io.Copy(b, dotreader.New(br)); e != nil {
		conn.Send("440 Failed reading body")
		return
	}

	if val := m.Get("X-Accept"); val == "DENY" {
		conn.Send("440 Deny test.")
		return
	}

	// 	b.String()
	if b.String() != "Body.\r\nBody1\r\nBody2 ohyeay?\r\n" {
		conn.Send("500 Body broken?")
		return
	}
	conn.Send("240 Posted " + m.Get("X-MsgId"))

	/*buf := new(bytes.Buffer)
	br := dotreader.New(conn.GetReader())

	// TODO: Unsafe copy ALL
	fmt.Println("PostArticle read.")
	if _, e := io.Copy(buf, br); e != nil {
		conn.Send("500 Copy error.")
		return
	}
	fmt.Println("PostArticle parse.")
	headBody := strings.SplitN(buf.String(), "\r\n\r\n", 2)

	r := textproto.NewReader(bufio.NewReader(strings.NewReader(headBody[0])))
	m, e := r.ReadMIMEHeader()
	if e != nil {
		conn.Send("441 Failed reading header")
		return
	}
	if val := m.Get("X-Accept"); val == "DENY" {
		conn.Send("441 Deny test.")
		return
	}
	fmt.Println("PostArticle body.")

	if headBody[1] != "Body.\r\nBody1\r\nBody2 ohyeay?\r\n" {
		conn.Send("500 Body broken?")
		return
	}

	conn.Send("240 Posted " + m.Get("X-MsgId"))*/
}

func Article(conn *client.Conn, tok []string) {
	if len(tok) != 2 {
		conn.Send("501 Invalid syntax.")
		return
	}
	read(conn, tok[1], "ARTICLE")
}

func Head(conn *client.Conn, tok []string) {
	if len(tok) != 2 {
		conn.Send("501 Invalid syntax.")
		return
	}
	read(conn, tok[1], "HEAD")
}

func Body(conn *client.Conn, tok []string) {
	if len(tok) != 2 {
		conn.Send("501 Invalid syntax.")
		return
	}
	read(conn, tok[1], "BODY")
}

func req(conn *client.Conn) {
	conn.Send("200 StoreD")
	for {
		tok, e := conn.ReadLine()
		if e != nil {
			fmt.Println(fmt.Sprintf("WARN: C(%s): %s", conn.RemoteAddr(), e.Error()))
			break
		}

		cmd := strings.ToUpper(tok[0])
		if cmd == "QUIT" {
			Quit(conn, tok)
			break
		} else if cmd == "ARTICLE" {
			Article(conn, tok)
		} else if cmd == "HEAD" {
			Head(conn, tok)
		} else if cmd == "BODY" {
			Body(conn, tok)
		} else if cmd == "AUTHINFO" {
			sub := strings.ToUpper(tok[1])
			if sub == "USER" {
				conn.Send("381 Need more.")
			} else if sub == "PASS" {
				if tok[2] == "test" {
					conn.Send("281 Authentication accepted.")
				}
			}
		} else if cmd == "NOOP" {
			conn.Send("500 Unsupported.")
		} else if cmd == "POST" {
			PostArticle(conn)
		} else {
			Unsupported(conn, tok)
			break
		}
	}

	conn.Close()
	if config.Verbose {
		fmt.Println(fmt.Sprintf("C(%s) Closed", conn.RemoteAddr()))
	}
}

func nntpListen(listen string) error {
	sock, err := net.Listen("tcp", listen)
	if err != nil {
		return err
	}
	if config.Verbose {
		fmt.Println("nntpd listening on " + listen)
	}

	for {
		conn, err := sock.Accept()
		if err != nil {
			panic(err)
		}
		if config.Verbose {
			fmt.Println(fmt.Sprintf("C(%s) New", conn.RemoteAddr()))
		}

		go req(client.New(conn))
	}
}<commit_msg>Feature. More msgids that change behaviour<commit_after>package main

import (
	"fmt"
	"spool-mock/config"
	"net"
	"spool-mock/client"
	"strings"
	//"spool-mock/db"
	"io"
	"net/textproto"
	"bufio"
	"bytes"
	"spool-mock/dotreader"
	//"spool-mock/headreader"
	//"spool-mock/bodyreader"
)

func Quit(conn *client.Conn, tok []string) {
	conn.Send("205 Bye.")
}

func Unsupported(conn *client.Conn, tok []string) {
	fmt.Println(fmt.Sprintf("WARN: C(%s): Unsupported cmd %s", conn.RemoteAddr(), tok[0]))
	conn.Send("500 Unsupported.")
}

func read(conn *client.Conn, msgid string, msgtype string) {
	var code string
	if msgtype == "ARTICLE" {
		code = "220"
	} else if msgtype == "HEAD" {
		code = "221"
	} else if msgtype == "BODY" {
		code = "222"
	} else {
		panic("Should not get here")
	}

	if msgid == "<aaa@bb.cc>" {
		conn.Send("500 msgid means fivehundred err")
		return
	}

	raw := `Path: asg009!abp002.ams.xsnews.nl!abuse.newsxs.nl!not-for-mail
From: Zinitzio <x8F4zpNLByt8Vhh1hyFBTcarWqKeqTszySrxYJUNrGyj64VA761YahKczcyROsOv.N5UyksLragucHTY7hXbIf3OraQSwtjjJX6PcYubvlsh6oPDUGuY1j0b4Z7i6xnio@47a00b01.16110764.10.1443172883.1.NL.v8r0DMvyrMxvrV9wjB9RklWe-p-p1ZChfS4lxGsMNtRWMbyLXZonEJ6Lp3usHDsLnG>
Subject: Mkv Tool Nix 8.4.0 NL | Zgp
Newsgroups: free.pt
Message-ID: <pTgQyybcKwYEhIFVg2wH7@spot.net>
X-Newsreader: Spotnet 2.0.0.114
X-XML: <Spotnet><Posting><Key>7</Key><Created>1443172883</Created><Poster>Zinitzio</Poster><Tag>Zgp</Tag><Title>Mkv Tool Nix 8.4.0 NL</Title><Description>Iedere Mkv (x264) film heeft meerdere sporen. Met dit programma kun je sporen verwijderen of toevoegen. Heb je een film zonder ondertitel dan kun je die makkelijk toevoegen.[br][br]In deze spot zitten de volgende onderdelen:[br][br]Mkv Tool Nix 8.4.0</Description><Image Width='350' Height='350'><Segment>Ldqj0ABsZDMEhIFVgyrLc@spot.net</Segment></Image><Size>16110764</Size><Category>0404a0004b01</Category><NZB><Segment>sm0Ls136Ir4EhIFVgj4Dg@spot.net</Segment></NZB></Posting></Spotnet>
X-XML-Signature: mMXtDVvEzuAz5soJzKcpsd042VQY2M306o418-pOYtLIxv7DN5lDzAO3rB3EakfZT
X-User-Key: <RSAKeyValue><Modulus>x8F4zpNLByt8Vhh1hyFBTcarWqKeqTszySrxYJUNrGyj64VA761YahKczcyROsOv</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>
X-User-Signature: N5UyksLragucHTY7hXbIf3OraQSwtjjJX6PcYubvlsh6oPDUGuY1j0b4Z7i6xnio
Content-Type: text/plain; charset=ISO-8859-1
Content-Transfer-Encoding: 8bit
X-Complaints-To: abuse@newsxs.nl
Organization: Newsxs
Date: Fri, 25 Sep 2015 11:21:23 +0200
Lines: 5
NNTP-Posting-Date: Fri, 25 Sep 2015 11:21:23 +0200

Iedere Mkv (x264) film heeft meerdere sporen. Met dit programma kun je sporen verwijderen of toevoegen. Heb je een film zonder ondertitel dan kun je die makkelijk toevoegen.

In deze spot zitten de volgende onderdelen:

Mkv Tool Nix 8.4.0`
	raw = strings.Replace(raw, "\n", "\r\n", -1)

	conn.Send(code + " " + msgid)

	if msgid == "<aab@bb.cc>" {
		// fake a broken
		conn.Send(raw[0:50])
		conn.Close()
	} else {
		conn.Send(raw)
	}
	conn.Send("\r\n.") // additional \r\n auto-added
	if msgid == "<close@bb.cc>" {
		conn.Close()
	}
}

func PostArticle(conn *client.Conn) {
	conn.Send("340 Start posting.")

	b := new(bytes.Buffer)
	br := bufio.NewReader(conn.GetReader())
	r := textproto.NewReader(br)

	fmt.Println("PostArticle head.")
	m, e := r.ReadMIMEHeader()
	if e != nil {
		conn.Send("440 Failed reading header")
		return
	}

	fmt.Println("PostArticle body.")
	if _, e := io.Copy(b, dotreader.New(br)); e != nil {
		conn.Send("440 Failed reading body")
		return
	}

	if val := m.Get("X-Accept"); val == "DENY" {
		conn.Send("440 Deny test.")
		return
	}

	if b.String() != "\r\nBody.\r\nBody1\r\nBody2 ohyeay?\r\n.\r\n" {
		conn.Send("500 Body does not match hardcoded compare value.")
		return
	}
	conn.Send("240 Posted.")
}

func Article(conn *client.Conn, tok []string) {
	if len(tok) != 2 {
		conn.Send("501 Invalid syntax.")
		return
	}
	read(conn, tok[1], "ARTICLE")
}

func Head(conn *client.Conn, tok []string) {
	if len(tok) != 2 {
		conn.Send("501 Invalid syntax.")
		return
	}
	read(conn, tok[1], "HEAD")
}

func Body(conn *client.Conn, tok []string) {
	if len(tok) != 2 {
		conn.Send("501 Invalid syntax.")
		return
	}
	read(conn, tok[1], "BODY")
}

func req(conn *client.Conn) {
	conn.Send("200 StoreD")
	for {
		tok, e := conn.ReadLine()
		if e != nil {
			fmt.Println(fmt.Sprintf("WARN: C(%s): %s", conn.RemoteAddr(), e.Error()))
			break
		}

		cmd := strings.ToUpper(tok[0])
		if cmd == "QUIT" {
			Quit(conn, tok)
			break
		} else if cmd == "ARTICLE" {
			Article(conn, tok)
		} else if cmd == "HEAD" {
			Head(conn, tok)
		} else if cmd == "BODY" {
			Body(conn, tok)
		} else if cmd == "AUTHINFO" {
			sub := strings.ToUpper(tok[1])
			if sub == "USER" {
				conn.Send("381 Need more.")
			} else if sub == "PASS" {
				if tok[2] == "test" {
					conn.Send("281 Authentication accepted.")
				}
			}
		} else if cmd == "NOOP" {
			conn.Send("500 Unsupported.")
		} else if cmd == "POST" {
			PostArticle(conn)
		} else {
			Unsupported(conn, tok)
			break
		}
	}

	conn.Close()
	if config.Verbose {
		fmt.Println(fmt.Sprintf("C(%s) Closed", conn.RemoteAddr()))
	}
}

func nntpListen(listen string) error {
	sock, err := net.Listen("tcp", listen)
	if err != nil {
		return err
	}
	if config.Verbose {
		fmt.Println("nntpd listening on " + listen)
	}

	for {
		conn, err := sock.Accept()
		if err != nil {
			panic(err)
		}
		if config.Verbose {
			fmt.Println(fmt.Sprintf("C(%s) New", conn.RemoteAddr()))
		}

		go req(client.New(conn))
	}
}<|endoftext|>


<commit_before>package proxmox

import (
	"fmt"
	"net/url"
	"strconv"
)

type Node struct {
	Mem      float64 `json:"mem"`
	MaxDisk  float64 `json:"maxdisk"`
	Node     string  `json:"node"`
	MaxCPU   float64 `json:"maxcpu"`
	Uptime   float64 `json:"uptime"`
	Id       string  `json:"id"`
	CPU      float64 `json:"cpu"`
	Level    string  `json:"level"`
	NodeType string  `json:"nodetype"`
	Disk     float64 `json:"disk"`
	MaxMem   float64 `json:"maxmem"`
	Proxmox  ProxMox
}

type NodeList map[string]Node

type NodeListResult struct {
	Data NodeList `json:"data"`
}

func (node Node) Qemu() (QemuList, error) {
	var err error
	var data map[string]interface{}
	var list QemuList
	var vm QemuVM
	var results []interface{}

	//fmt.Println("!Qemu")

	data, err = node.Proxmox.Get("nodes/" + node.Node + "/qemu")
	if err != nil {
		return nil, err
	}

	list = make(QemuList)
	results = data["data"].([]interface{})
	for _, v0 := range results {
		v := v0.(map[string]interface{})
		VMIdFloat := v["vmid"].(float64)
		vm = QemuVM{
			Mem:    v["mem"].(float64),
			CPUs:   v["cpus"].(float64),
			NetOut: v["netout"].(float64),
			//			PID:       v["pid"].(string),
			Disk:      v["disk"].(float64),
			MaxMem:    v["maxmem"].(float64),
			Status:    v["status"].(string),
			NetIn:     v["netin"].(float64),
			MaxDisk:   v["maxdisk"].(float64),
			Name:      v["name"].(string),
			DiskWrite: v["diskwrite"].(float64),
			CPU:       v["cpu"].(float64),
			VMId:      VMIdFloat,
			DiskRead:  v["diskread"].(float64),
			Uptime:    v["uptime"].(float64),
			Node:      node,
		}

		switch v["template"].(type) {
		case float64:
			vm.Template = v["template"].(float64)
		default:
			vm.Template = 0
		}

		list[strconv.FormatFloat(vm.VMId, 'f', 0, 64)] = vm
	}

	return list, nil
}

func (node Node) MaxQemuId() (float64, error) {
	var list QemuList
	var vm QemuVM
	var id float64
	var err error

	//fmt.Println("!MaxQemuId")

	id = 1009
	list, err = node.Qemu()
	if err != nil {
		return 0, err
	}

	for _, vm = range list {
		if vm.VMId > id && vm.VMId >= 1010 && vm.VMId < 2000 {
			id = vm.VMId
		}
	}
	return id, nil
}

func (node Node) Storages() (StorageList, error) {
	var err error
	var data map[string]interface{}
	var list StorageList
	var storage Storage
	var results []interface{}

	//fmt.Println("!Storages")

	data, err = node.Proxmox.Get("nodes/" + node.Node + "/storage")
	if err != nil {
		return nil, err
	}
	list = make(StorageList)
	results = data["data"].([]interface{})
	for _, v0 := range results {
		v := v0.(map[string]interface{})
		storage = Storage{
			StorageType: v["type"].(string),
			Active:      v["active"].(float64),
			Total:       v["total"].(float64),
			Content:     v["content"].(string),
			Shared:      v["shared"].(float64),
			Storage:     v["storage"].(string),
			Used:        v["used"].(float64),
			Avail:       v["avail"].(float64),
			Node:        node,
		}
		list[storage.Storage] = storage
	}

	return list, nil
}

func (node Node) CreateQemuVM(Name string, Sockets int, Cores int, MemorySize int, DiskSize string) (string, error) {
	var err error
	var newVmId string
	var storageList StorageList
	//var storage Storage
	var results map[string]interface{}
	var storageId string
	var form url.Values
	var target string

	//fmt.Println("!CreateQemuVM")

	newVmId, err = node.Proxmox.NextVMId()
	if err != nil {
		return "", err
	}
	//fmt.Println("new VM ID: " + newVmId)
	storageList, err = node.Storages()
	results, err = storageList["local"].CreateVolume("vm-"+newVmId+"-disk-0.qcow2", DiskSize, newVmId)
	if err != nil {
		return "", err
	}
	storageId = results["data"].(string)

	//fmt.Println("!CreateVolume")

	form = url.Values{
		"vmid":    {newVmId},
		"memory":  {strconv.Itoa(MemorySize)},
		"sockets": {strconv.Itoa(Sockets)},
		"cores":   {strconv.Itoa(Cores)},
		"net0":    {"virtio,bridge=vmbr0"},
		"virtio0": {storageId},
	}
	if Name != "" {
		form.Set("name", Name)
	}

	target = "nodes/" + node.Node + "/qemu"
	results, err = node.Proxmox.PostForm(target, form)
	if err != nil {
		fmt.Println("Error creating VM!!!")
		return "", err
	}
	//fmt.Println("VM " + newVmId + " created")

	return newVmId, err
}

func (node Node) VZDump(VmId string, BWLimit int, Compress string, IONice int, LockWait int, Mode string) (string, error) {
	var form url.Values
	var target string
	var err error
	var results map[string]interface{}

	form = url.Values{
		"vmid":     {VmId},
		"compress": {Compress},
		"lockwait": {strconv.Itoa(LockWait)},
		"mode":     {Mode},
	}
	if BWLimit > 0 {
		form.Set("bwlimit", strconv.Itoa(BWLimit))
	}
	if IONice > 0 {
		form.Set("ionice", strconv.Itoa(IONice))
	}
	target = "nodes/" + node.Node + "/vzdump"
	results, err = node.Proxmox.PostForm(target, form)
	if err != nil {
		fmt.Println("Error dumping VM!")
		return "", err
	}
	return results["data"].(string), nil
}

func (node Node) Tasks(Limit int, Start int, UserFilter string, VmId string) (TaskList, error) {
	var err error
	var target string
	var data map[string]interface{}
	var list TaskList
	var task Task
	var results []interface{}

	//fmt.Println("!Tasks")
	target = "nodes/" + node.Node + "/tasks?"
	if Limit > 0 {
		target = target + "limit=" + strconv.Itoa(Limit) + "&"
	}
	if Start > 0 {
		target = target + "start=" + strconv.Itoa(Start) + "&"
	}
	if UserFilter != "" {
		target = target + "userfilter=" + UserFilter + "&"
	}
	if VmId != "" {
		target = target + "vmid=" + VmId + "&"
	}
	target = target[0 : len(target)-1]
	data, err = node.Proxmox.Get(target)
	if err != nil {
		return nil, err
	}
	list = make(TaskList)
	results = data["data"].([]interface{})
	for _, v0 := range results {
		v := v0.(map[string]interface{})
		task = Task{
			UPid:      v["upid"].(string),
			Type:      v["type"].(string),
			Status:    v["status"].(string),
			PID:       v["pid"].(float64),
			PStart:    v["pstart"].(float64),
			StartTime: v["starttime"].(float64),
			EndTime:   v["endtime"].(float64),
			ID:        v["id"].(string),
			proxmox:   node.Proxmox,
		}
		list[task.UPid] = task
	}

	return list, nil
}
<commit_msg>check type of v['vmid']<commit_after>package proxmox

import (
	"fmt"
	"net/url"
	"strconv"
)

type Node struct {
	Mem      float64 `json:"mem"`
	MaxDisk  float64 `json:"maxdisk"`
	Node     string  `json:"node"`
	MaxCPU   float64 `json:"maxcpu"`
	Uptime   float64 `json:"uptime"`
	Id       string  `json:"id"`
	CPU      float64 `json:"cpu"`
	Level    string  `json:"level"`
	NodeType string  `json:"nodetype"`
	Disk     float64 `json:"disk"`
	MaxMem   float64 `json:"maxmem"`
	Proxmox  ProxMox
}

type NodeList map[string]Node

type NodeListResult struct {
	Data NodeList `json:"data"`
}

func (node Node) Qemu() (QemuList, error) {
	var err error
	var data map[string]interface{}
	var list QemuList
	var vm QemuVM
	var results []interface{}
	var VMIdFloat float64

	//fmt.Println("!Qemu")

	data, err = node.Proxmox.Get("nodes/" + node.Node + "/qemu")
	if err != nil {
		return nil, err
	}

	list = make(QemuList)
	results = data["data"].([]interface{})
	for _, v0 := range results {
		v := v0.(map[string]interface{})
		if _, ok := v["vmid"].(string); ok {
			VMIdFloat, err = strconv.ParseFloat(v["vmid"].(string), 64)
			if err != nil {
				return nil, err
			} else {
				VMIdFloat = v["vmid"].(float64)
			}
		}
		vm = QemuVM{
			Mem:    v["mem"].(float64),
			CPUs:   v["cpus"].(float64),
			NetOut: v["netout"].(float64),
			//			PID:       v["pid"].(string),
			Disk:      v["disk"].(float64),
			MaxMem:    v["maxmem"].(float64),
			Status:    v["status"].(string),
			NetIn:     v["netin"].(float64),
			MaxDisk:   v["maxdisk"].(float64),
			Name:      v["name"].(string),
			DiskWrite: v["diskwrite"].(float64),
			CPU:       v["cpu"].(float64),
			VMId:      VMIdFloat,
			DiskRead:  v["diskread"].(float64),
			Uptime:    v["uptime"].(float64),
			Node:      node,
		}

		switch v["template"].(type) {
		case float64:
			vm.Template = v["template"].(float64)
		default:
			vm.Template = 0
		}

		list[strconv.FormatFloat(vm.VMId, 'f', 0, 64)] = vm
	}

	return list, nil
}

func (node Node) MaxQemuId() (float64, error) {
	var list QemuList
	var vm QemuVM
	var id float64
	var err error

	//fmt.Println("!MaxQemuId")

	id = 1009
	list, err = node.Qemu()
	if err != nil {
		return 0, err
	}

	for _, vm = range list {
		if vm.VMId > id && vm.VMId >= 1010 && vm.VMId < 2000 {
			id = vm.VMId
		}
	}
	return id, nil
}

func (node Node) Storages() (StorageList, error) {
	var err error
	var data map[string]interface{}
	var list StorageList
	var storage Storage
	var results []interface{}

	//fmt.Println("!Storages")

	data, err = node.Proxmox.Get("nodes/" + node.Node + "/storage")
	if err != nil {
		return nil, err
	}
	list = make(StorageList)
	results = data["data"].([]interface{})
	for _, v0 := range results {
		v := v0.(map[string]interface{})
		storage = Storage{
			StorageType: v["type"].(string),
			Active:      v["active"].(float64),
			Total:       v["total"].(float64),
			Content:     v["content"].(string),
			Shared:      v["shared"].(float64),
			Storage:     v["storage"].(string),
			Used:        v["used"].(float64),
			Avail:       v["avail"].(float64),
			Node:        node,
		}
		list[storage.Storage] = storage
	}

	return list, nil
}

func (node Node) CreateQemuVM(Name string, Sockets int, Cores int, MemorySize int, DiskSize string) (string, error) {
	var err error
	var newVmId string
	var storageList StorageList
	//var storage Storage
	var results map[string]interface{}
	var storageId string
	var form url.Values
	var target string

	//fmt.Println("!CreateQemuVM")

	newVmId, err = node.Proxmox.NextVMId()
	if err != nil {
		return "", err
	}
	//fmt.Println("new VM ID: " + newVmId)
	storageList, err = node.Storages()
	results, err = storageList["local"].CreateVolume("vm-"+newVmId+"-disk-0.qcow2", DiskSize, newVmId)
	if err != nil {
		return "", err
	}
	storageId = results["data"].(string)

	//fmt.Println("!CreateVolume")

	form = url.Values{
		"vmid":    {newVmId},
		"memory":  {strconv.Itoa(MemorySize)},
		"sockets": {strconv.Itoa(Sockets)},
		"cores":   {strconv.Itoa(Cores)},
		"net0":    {"virtio,bridge=vmbr0"},
		"virtio0": {storageId},
	}
	if Name != "" {
		form.Set("name", Name)
	}

	target = "nodes/" + node.Node + "/qemu"
	results, err = node.Proxmox.PostForm(target, form)
	if err != nil {
		fmt.Println("Error creating VM!!!")
		return "", err
	}
	//fmt.Println("VM " + newVmId + " created")

	return newVmId, err
}

func (node Node) VZDump(VmId string, BWLimit int, Compress string, IONice int, LockWait int, Mode string) (string, error) {
	var form url.Values
	var target string
	var err error
	var results map[string]interface{}

	form = url.Values{
		"vmid":     {VmId},
		"compress": {Compress},
		"lockwait": {strconv.Itoa(LockWait)},
		"mode":     {Mode},
	}
	if BWLimit > 0 {
		form.Set("bwlimit", strconv.Itoa(BWLimit))
	}
	if IONice > 0 {
		form.Set("ionice", strconv.Itoa(IONice))
	}
	target = "nodes/" + node.Node + "/vzdump"
	results, err = node.Proxmox.PostForm(target, form)
	if err != nil {
		fmt.Println("Error dumping VM!")
		return "", err
	}
	return results["data"].(string), nil
}

func (node Node) Tasks(Limit int, Start int, UserFilter string, VmId string) (TaskList, error) {
	var err error
	var target string
	var data map[string]interface{}
	var list TaskList
	var task Task
	var results []interface{}

	//fmt.Println("!Tasks")
	target = "nodes/" + node.Node + "/tasks?"
	if Limit > 0 {
		target = target + "limit=" + strconv.Itoa(Limit) + "&"
	}
	if Start > 0 {
		target = target + "start=" + strconv.Itoa(Start) + "&"
	}
	if UserFilter != "" {
		target = target + "userfilter=" + UserFilter + "&"
	}
	if VmId != "" {
		target = target + "vmid=" + VmId + "&"
	}
	target = target[0 : len(target)-1]
	data, err = node.Proxmox.Get(target)
	if err != nil {
		return nil, err
	}
	list = make(TaskList)
	results = data["data"].([]interface{})
	for _, v0 := range results {
		v := v0.(map[string]interface{})
		task = Task{
			UPid:      v["upid"].(string),
			Type:      v["type"].(string),
			Status:    v["status"].(string),
			PID:       v["pid"].(float64),
			PStart:    v["pstart"].(float64),
			StartTime: v["starttime"].(float64),
			EndTime:   v["endtime"].(float64),
			ID:        v["id"].(string),
			proxmox:   node.Proxmox,
		}
		list[task.UPid] = task
	}

	return list, nil
}
<|endoftext|>