<commit_before>// +build !windows

package termbox

import "github.com/mattn/go-runewidth"
import "fmt"
import "os"
import "os/signal"
import "syscall"
import "runtime"

// public API

// Initializes termbox library. This function should be called before any other functions.
// After successful initialization, the library must be finalized using 'Close' function.
//
// Example usage:
//      err := termbox.Init()
//      if err != nil {
//              panic(err)
//      }
//      defer termbox.Close()
func Init() error {
	var err error

	out, err = os.OpenFile("/dev/tty", syscall.O_WRONLY, 0)
	if err != nil {
		return err
	}
	in, err = syscall.Open("/dev/tty", syscall.O_RDONLY, 0)
	if err != nil {
		return err
	}

	err = setup_term()
	if err != nil {
		return fmt.Errorf("termbox: error while reading terminfo data: %v", err)
	}

	signal.Notify(sigwinch, syscall.SIGWINCH)
	signal.Notify(sigio, syscall.SIGIO)

	_, err = fcntl(in, syscall.F_SETFL, syscall.O_ASYNC|syscall.O_NONBLOCK)
	if err != nil {
		return err
	}
	_, err = fcntl(in, syscall.F_SETOWN, syscall.Getpid())
	if runtime.GOOS != "darwin" && err != nil {
		return err
	}
	err = tcgetattr(out.Fd(), &orig_tios)
	if err != nil {
		return err
	}

	tios := orig_tios
	tios.Iflag &^= syscall_IGNBRK | syscall_BRKINT | syscall_PARMRK |
		syscall_ISTRIP | syscall_INLCR | syscall_IGNCR |
		syscall_ICRNL | syscall_IXON
	tios.Oflag &^= syscall_OPOST
	tios.Lflag &^= syscall_ECHO | syscall_ECHONL | syscall_ICANON |
		syscall_ISIG | syscall_IEXTEN
	tios.Cflag &^= syscall_CSIZE | syscall_PARENB
	tios.Cflag |= syscall_CS8
	tios.Cc[syscall_VMIN] = 1
	tios.Cc[syscall_VTIME] = 0

	err = tcsetattr(out.Fd(), &tios)
	if err != nil {
		return err
	}

	out.WriteString(funcs[t_enter_ca])
	out.WriteString(funcs[t_enter_keypad])
	out.WriteString(funcs[t_hide_cursor])
	out.WriteString(funcs[t_clear_screen])

	termw, termh = get_term_size(out.Fd())
	back_buffer.init(termw, termh)
	front_buffer.init(termw, termh)
	back_buffer.clear()
	front_buffer.clear()

	go func() {
		buf := make([]byte, 128)
		for {
			select {
			case <-sigio:
				for {
					n, err := syscall.Read(in, buf)
					if err == syscall.EAGAIN || err == syscall.EWOULDBLOCK {
						break
					}
					select {
					case input_comm <- input_event{buf[:n], err}:
						ie := <-input_comm
						buf = ie.data[:128]
					case <-quit:
						return
					}
				}
			case <-quit:
				return
			}
		}
	}()

	IsInit = true
	return nil
}

// Interrupt an in-progress call to PollEvent by causing it to return
// EventInterrupt.  Note that this function will block until the PollEvent
// function has successfully been interrupted.
func Interrupt() {
	interrupt_comm <- struct{}{}
}

// Finalizes termbox library, should be called after successful initialization
// when termbox's functionality isn't required anymore.
func Close() {
	quit <- 1
	out.WriteString(funcs[t_show_cursor])
	out.WriteString(funcs[t_sgr0])
	out.WriteString(funcs[t_clear_screen])
	out.WriteString(funcs[t_exit_ca])
	out.WriteString(funcs[t_exit_keypad])
	out.WriteString(funcs[t_exit_mouse])
	tcsetattr(out.Fd(), &orig_tios)

	out.Close()
	syscall.Close(in)

	// reset the state, so that on next Init() it will work again
	termw = 0
	termh = 0
	input_mode = InputEsc
	out = nil
	in = 0
	lastfg = attr_invalid
	lastbg = attr_invalid
	lastx = coord_invalid
	lasty = coord_invalid
	cursor_x = cursor_hidden
	cursor_y = cursor_hidden
	foreground = ColorDefault
	background = ColorDefault
	IsInit = false
}

// Synchronizes the internal back buffer with the terminal.
func Flush() error {
	// invalidate cursor position
	lastx = coord_invalid
	lasty = coord_invalid

	update_size_maybe()

	for y := 0; y < front_buffer.height; y++ {
		line_offset := y * front_buffer.width
		for x := 0; x < front_buffer.width; {
			cell_offset := line_offset + x
			back := &back_buffer.cells[cell_offset]
			front := &front_buffer.cells[cell_offset]
			if back.Ch < ' ' {
				back.Ch = ' '
			}
			w := runewidth.RuneWidth(back.Ch)
			if w == 0 || w == 2 && runewidth.IsAmbiguousWidth(back.Ch) {
				w = 1
			}
			if *back == *front {
				x += w
				continue
			}
			*front = *back
			send_attr(back.Fg, back.Bg)

			if w == 2 && x == front_buffer.width-1 {
				// there's not enough space for 2-cells rune,
				// let's just put a space in there
				send_char(x, y, ' ')
			} else {
				send_char(x, y, back.Ch)
				if w == 2 {
					next := cell_offset + 1
					front_buffer.cells[next] = Cell{
						Ch: 0,
						Fg: back.Fg,
						Bg: back.Bg,
					}
				}
			}
			x += w
		}
	}
	if !is_cursor_hidden(cursor_x, cursor_y) {
		write_cursor(cursor_x, cursor_y)
	}
	return flush()
}

// Sets the position of the cursor. See also HideCursor().
func SetCursor(x, y int) {
	if is_cursor_hidden(cursor_x, cursor_y) && !is_cursor_hidden(x, y) {
		outbuf.WriteString(funcs[t_show_cursor])
	}

	if !is_cursor_hidden(cursor_x, cursor_y) && is_cursor_hidden(x, y) {
		outbuf.WriteString(funcs[t_hide_cursor])
	}

	cursor_x, cursor_y = x, y
	if !is_cursor_hidden(cursor_x, cursor_y) {
		write_cursor(cursor_x, cursor_y)
	}
}

// The shortcut for SetCursor(-1, -1).
func HideCursor() {
	SetCursor(cursor_hidden, cursor_hidden)
}

// Changes cell's parameters in the internal back buffer at the specified
// position.
func SetCell(x, y int, ch rune, fg, bg Attribute) {
	if x < 0 || x >= back_buffer.width {
		return
	}
	if y < 0 || y >= back_buffer.height {
		return
	}

	back_buffer.cells[y*back_buffer.width+x] = Cell{ch, fg, bg}
}

// Returns a slice into the termbox's back buffer. You can get its dimensions
// using 'Size' function. The slice remains valid as long as no 'Clear' or
// 'Flush' function calls were made after call to this function.
func CellBuffer() []Cell {
	return back_buffer.cells
}

// After getting a raw event from PollRawEvent function call, you can parse it
// again into an ordinary one using termbox logic. That is parse an event as
// termbox would do it. Returned event in addition to usual Event struct fields
// sets N field to the amount of bytes used within 'data' slice. If the length
// of 'data' slice is zero or event cannot be parsed for some other reason, the
// function will return a special event type: EventNone.
//
// IMPORTANT: EventNone may contain a non-zero N, which means you should skip
// these bytes, because termbox cannot recognize them.
//
// NOTE: This API is experimental and may change in future.
func ParseEvent(data []byte) Event {
	event := Event{Type: EventKey}
	ok := extract_event(data, &event)
	if !ok {
		return Event{Type: EventNone, N: event.N}
	}
	return event
}

// Wait for an event and return it. This is a blocking function call. Instead
// of EventKey and EventMouse it returns EventRaw events. Raw event is written
// into `data` slice and Event's N field is set to the amount of bytes written.
// The minimum required length of the 'data' slice is 1. This requirement may
// vary on different platforms.
//
// NOTE: This API is experimental and may change in future.
func PollRawEvent(data []byte) Event {
	if len(data) == 0 {
		panic("len(data) >= 1 is a requirement")
	}

	var event Event
	if extract_raw_event(data, &event) {
		return event
	}

	for {
		select {
		case ev := <-input_comm:
			if ev.err != nil {
				return Event{Type: EventError, Err: ev.err}
			}

			inbuf = append(inbuf, ev.data...)
			input_comm <- ev
			if extract_raw_event(data, &event) {
				return event
			}
		case <-interrupt_comm:
			event.Type = EventInterrupt
			return event

		case <-sigwinch:
			event.Type = EventResize
			event.Width, event.Height = get_term_size(out.Fd())
			return event
		}
	}
}

// Wait for an event and return it. This is a blocking function call.
func PollEvent() Event {
	var event Event

	// try to extract event from input buffer, return on success
	event.Type = EventKey
	ok := extract_event(inbuf, &event)
	if event.N != 0 {
		copy(inbuf, inbuf[event.N:])
		inbuf = inbuf[:len(inbuf)-event.N]
	}
	if ok {
		return event
	}

	for {
		select {
		case ev := <-input_comm:
			if ev.err != nil {
				return Event{Type: EventError, Err: ev.err}
			}

			inbuf = append(inbuf, ev.data...)
			input_comm <- ev
			ok := extract_event(inbuf, &event)
			if event.N != 0 {
				copy(inbuf, inbuf[event.N:])
				inbuf = inbuf[:len(inbuf)-event.N]
			}
			if ok {
				return event
			}
		case <-interrupt_comm:
			event.Type = EventInterrupt
			return event

		case <-sigwinch:
			event.Type = EventResize
			event.Width, event.Height = get_term_size(out.Fd())
			return event
		}
	}
	panic("unreachable")
}

// Returns the size of the internal back buffer (which is mostly the same as
// terminal's window size in characters). But it doesn't always match the size
// of the terminal window, after the terminal size has changed, the internal
// back buffer will get in sync only after Clear or Flush function calls.
func Size() (int, int) {
	return termw, termh
}

// Clears the internal back buffer.
func Clear(fg, bg Attribute) error {
	foreground, background = fg, bg
	err := update_size_maybe()
	back_buffer.clear()
	return err
}

// Sets termbox input mode. Termbox has two input modes:
//
// 1. Esc input mode. When ESC sequence is in the buffer and it doesn't match
// any known sequence. ESC means KeyEsc. This is the default input mode.
//
// 2. Alt input mode. When ESC sequence is in the buffer and it doesn't match
// any known sequence. ESC enables ModAlt modifier for the next keyboard event.
//
// Both input modes can be OR'ed with Mouse mode. Setting Mouse mode bit up will
// enable mouse button click events.
//
// If 'mode' is InputCurrent, returns the current input mode. See also Input*
// constants.
func SetInputMode(mode InputMode) InputMode {
	if mode == InputCurrent {
		return input_mode
	}
	if mode&InputMouse != 0 {
		out.WriteString(funcs[t_enter_mouse])
	} else {
		out.WriteString(funcs[t_exit_mouse])
	}

	input_mode = mode
	return input_mode
}

// Sets the termbox output mode. Termbox has four output options:
// 1. OutputNormal => [1..8]
//    This mode provides 8 different colors:
//        black, red, green, yellow, blue, magenta, cyan, white
//    Shortcut: ColorBlack, ColorRed, ...
//    Attributes: AttrBold, AttrUnderline, AttrReverse
//
//    Example usage:
//        SetCell(x, y, '@', ColorBlack | AttrBold, ColorRed);
//
// 2. Output256 => [1..256]
//    In this mode you can leverage the 256 terminal mode:
//    0x00 - 0x07: the 8 colors as in OutputNormal
//    0x08 - 0x0f: Color* | AttrBold
//    0x10 - 0xe7: 216 different colors
//    0xe8 - 0xff: 24 different shades of grey
//
//    Example usage:
//        SetCell(x, y, '@', 184, 240);
//        SetCell(x, y, '@', 0xb8, 0xf0);
//
// 3. Output216 => [1..216]
//    This mode supports the 3rd range of the 256 mode only.
//    But you dont need to provide an offset.
//
// 4. OutputGrayscale => [1..24]
//    This mode supports the 4th range of the 256 mode only.
//    But you dont need to provide an offset.
//
// In all modes, 0 represents the default color.
//
// `go run _demos/output.go` to see its impact on your terminal.
//
// If 'mode' is OutputCurrent, it returns the current output mode.
//
// Note that this may return a different OutputMode than the one requested,
// as the requested mode may not be available on the target platform.
func SetOutputMode(mode OutputMode) OutputMode {
	if mode == OutputCurrent {
		return output_mode
	}

	output_mode = mode
	return output_mode
}

// Sync comes handy when something causes desync between termbox's understanding
// of a terminal buffer and the reality. Such as a third party process. Sync
// forces a complete resync between the termbox and a terminal, it may not be
// visually pretty though.
func Sync() error {
	front_buffer.clear()
	err := send_clear()
	if err != nil {
		return err
	}

	return Flush()
}
<commit_msg>Document the return values of Size<commit_after>// +build !windows

package termbox

import "github.com/mattn/go-runewidth"
import "fmt"
import "os"
import "os/signal"
import "syscall"
import "runtime"

// public API

// Initializes termbox library. This function should be called before any other functions.
// After successful initialization, the library must be finalized using 'Close' function.
//
// Example usage:
//      err := termbox.Init()
//      if err != nil {
//              panic(err)
//      }
//      defer termbox.Close()
func Init() error {
	var err error

	out, err = os.OpenFile("/dev/tty", syscall.O_WRONLY, 0)
	if err != nil {
		return err
	}
	in, err = syscall.Open("/dev/tty", syscall.O_RDONLY, 0)
	if err != nil {
		return err
	}

	err = setup_term()
	if err != nil {
		return fmt.Errorf("termbox: error while reading terminfo data: %v", err)
	}

	signal.Notify(sigwinch, syscall.SIGWINCH)
	signal.Notify(sigio, syscall.SIGIO)

	_, err = fcntl(in, syscall.F_SETFL, syscall.O_ASYNC|syscall.O_NONBLOCK)
	if err != nil {
		return err
	}
	_, err = fcntl(in, syscall.F_SETOWN, syscall.Getpid())
	if runtime.GOOS != "darwin" && err != nil {
		return err
	}
	err = tcgetattr(out.Fd(), &orig_tios)
	if err != nil {
		return err
	}

	tios := orig_tios
	tios.Iflag &^= syscall_IGNBRK | syscall_BRKINT | syscall_PARMRK |
		syscall_ISTRIP | syscall_INLCR | syscall_IGNCR |
		syscall_ICRNL | syscall_IXON
	tios.Oflag &^= syscall_OPOST
	tios.Lflag &^= syscall_ECHO | syscall_ECHONL | syscall_ICANON |
		syscall_ISIG | syscall_IEXTEN
	tios.Cflag &^= syscall_CSIZE | syscall_PARENB
	tios.Cflag |= syscall_CS8
	tios.Cc[syscall_VMIN] = 1
	tios.Cc[syscall_VTIME] = 0

	err = tcsetattr(out.Fd(), &tios)
	if err != nil {
		return err
	}

	out.WriteString(funcs[t_enter_ca])
	out.WriteString(funcs[t_enter_keypad])
	out.WriteString(funcs[t_hide_cursor])
	out.WriteString(funcs[t_clear_screen])

	termw, termh = get_term_size(out.Fd())
	back_buffer.init(termw, termh)
	front_buffer.init(termw, termh)
	back_buffer.clear()
	front_buffer.clear()

	go func() {
		buf := make([]byte, 128)
		for {
			select {
			case <-sigio:
				for {
					n, err := syscall.Read(in, buf)
					if err == syscall.EAGAIN || err == syscall.EWOULDBLOCK {
						break
					}
					select {
					case input_comm <- input_event{buf[:n], err}:
						ie := <-input_comm
						buf = ie.data[:128]
					case <-quit:
						return
					}
				}
			case <-quit:
				return
			}
		}
	}()

	IsInit = true
	return nil
}

// Interrupt an in-progress call to PollEvent by causing it to return
// EventInterrupt.  Note that this function will block until the PollEvent
// function has successfully been interrupted.
func Interrupt() {
	interrupt_comm <- struct{}{}
}

// Finalizes termbox library, should be called after successful initialization
// when termbox's functionality isn't required anymore.
func Close() {
	quit <- 1
	out.WriteString(funcs[t_show_cursor])
	out.WriteString(funcs[t_sgr0])
	out.WriteString(funcs[t_clear_screen])
	out.WriteString(funcs[t_exit_ca])
	out.WriteString(funcs[t_exit_keypad])
	out.WriteString(funcs[t_exit_mouse])
	tcsetattr(out.Fd(), &orig_tios)

	out.Close()
	syscall.Close(in)

	// reset the state, so that on next Init() it will work again
	termw = 0
	termh = 0
	input_mode = InputEsc
	out = nil
	in = 0
	lastfg = attr_invalid
	lastbg = attr_invalid
	lastx = coord_invalid
	lasty = coord_invalid
	cursor_x = cursor_hidden
	cursor_y = cursor_hidden
	foreground = ColorDefault
	background = ColorDefault
	IsInit = false
}

// Synchronizes the internal back buffer with the terminal.
func Flush() error {
	// invalidate cursor position
	lastx = coord_invalid
	lasty = coord_invalid

	update_size_maybe()

	for y := 0; y < front_buffer.height; y++ {
		line_offset := y * front_buffer.width
		for x := 0; x < front_buffer.width; {
			cell_offset := line_offset + x
			back := &back_buffer.cells[cell_offset]
			front := &front_buffer.cells[cell_offset]
			if back.Ch < ' ' {
				back.Ch = ' '
			}
			w := runewidth.RuneWidth(back.Ch)
			if w == 0 || w == 2 && runewidth.IsAmbiguousWidth(back.Ch) {
				w = 1
			}
			if *back == *front {
				x += w
				continue
			}
			*front = *back
			send_attr(back.Fg, back.Bg)

			if w == 2 && x == front_buffer.width-1 {
				// there's not enough space for 2-cells rune,
				// let's just put a space in there
				send_char(x, y, ' ')
			} else {
				send_char(x, y, back.Ch)
				if w == 2 {
					next := cell_offset + 1
					front_buffer.cells[next] = Cell{
						Ch: 0,
						Fg: back.Fg,
						Bg: back.Bg,
					}
				}
			}
			x += w
		}
	}
	if !is_cursor_hidden(cursor_x, cursor_y) {
		write_cursor(cursor_x, cursor_y)
	}
	return flush()
}

// Sets the position of the cursor. See also HideCursor().
func SetCursor(x, y int) {
	if is_cursor_hidden(cursor_x, cursor_y) && !is_cursor_hidden(x, y) {
		outbuf.WriteString(funcs[t_show_cursor])
	}

	if !is_cursor_hidden(cursor_x, cursor_y) && is_cursor_hidden(x, y) {
		outbuf.WriteString(funcs[t_hide_cursor])
	}

	cursor_x, cursor_y = x, y
	if !is_cursor_hidden(cursor_x, cursor_y) {
		write_cursor(cursor_x, cursor_y)
	}
}

// The shortcut for SetCursor(-1, -1).
func HideCursor() {
	SetCursor(cursor_hidden, cursor_hidden)
}

// Changes cell's parameters in the internal back buffer at the specified
// position.
func SetCell(x, y int, ch rune, fg, bg Attribute) {
	if x < 0 || x >= back_buffer.width {
		return
	}
	if y < 0 || y >= back_buffer.height {
		return
	}

	back_buffer.cells[y*back_buffer.width+x] = Cell{ch, fg, bg}
}

// Returns a slice into the termbox's back buffer. You can get its dimensions
// using 'Size' function. The slice remains valid as long as no 'Clear' or
// 'Flush' function calls were made after call to this function.
func CellBuffer() []Cell {
	return back_buffer.cells
}

// After getting a raw event from PollRawEvent function call, you can parse it
// again into an ordinary one using termbox logic. That is parse an event as
// termbox would do it. Returned event in addition to usual Event struct fields
// sets N field to the amount of bytes used within 'data' slice. If the length
// of 'data' slice is zero or event cannot be parsed for some other reason, the
// function will return a special event type: EventNone.
//
// IMPORTANT: EventNone may contain a non-zero N, which means you should skip
// these bytes, because termbox cannot recognize them.
//
// NOTE: This API is experimental and may change in future.
func ParseEvent(data []byte) Event {
	event := Event{Type: EventKey}
	ok := extract_event(data, &event)
	if !ok {
		return Event{Type: EventNone, N: event.N}
	}
	return event
}

// Wait for an event and return it. This is a blocking function call. Instead
// of EventKey and EventMouse it returns EventRaw events. Raw event is written
// into `data` slice and Event's N field is set to the amount of bytes written.
// The minimum required length of the 'data' slice is 1. This requirement may
// vary on different platforms.
//
// NOTE: This API is experimental and may change in future.
func PollRawEvent(data []byte) Event {
	if len(data) == 0 {
		panic("len(data) >= 1 is a requirement")
	}

	var event Event
	if extract_raw_event(data, &event) {
		return event
	}

	for {
		select {
		case ev := <-input_comm:
			if ev.err != nil {
				return Event{Type: EventError, Err: ev.err}
			}

			inbuf = append(inbuf, ev.data...)
			input_comm <- ev
			if extract_raw_event(data, &event) {
				return event
			}
		case <-interrupt_comm:
			event.Type = EventInterrupt
			return event

		case <-sigwinch:
			event.Type = EventResize
			event.Width, event.Height = get_term_size(out.Fd())
			return event
		}
	}
}

// Wait for an event and return it. This is a blocking function call.
func PollEvent() Event {
	var event Event

	// try to extract event from input buffer, return on success
	event.Type = EventKey
	ok := extract_event(inbuf, &event)
	if event.N != 0 {
		copy(inbuf, inbuf[event.N:])
		inbuf = inbuf[:len(inbuf)-event.N]
	}
	if ok {
		return event
	}

	for {
		select {
		case ev := <-input_comm:
			if ev.err != nil {
				return Event{Type: EventError, Err: ev.err}
			}

			inbuf = append(inbuf, ev.data...)
			input_comm <- ev
			ok := extract_event(inbuf, &event)
			if event.N != 0 {
				copy(inbuf, inbuf[event.N:])
				inbuf = inbuf[:len(inbuf)-event.N]
			}
			if ok {
				return event
			}
		case <-interrupt_comm:
			event.Type = EventInterrupt
			return event

		case <-sigwinch:
			event.Type = EventResize
			event.Width, event.Height = get_term_size(out.Fd())
			return event
		}
	}
	panic("unreachable")
}

// Returns the size of the internal back buffer (which is mostly the same as
// terminal's window size in characters). But it doesn't always match the size
// of the terminal window, after the terminal size has changed, the internal
// back buffer will get in sync only after Clear or Flush function calls.
func Size() (width int, height int) {
	return termw, termh
}

// Clears the internal back buffer.
func Clear(fg, bg Attribute) error {
	foreground, background = fg, bg
	err := update_size_maybe()
	back_buffer.clear()
	return err
}

// Sets termbox input mode. Termbox has two input modes:
//
// 1. Esc input mode. When ESC sequence is in the buffer and it doesn't match
// any known sequence. ESC means KeyEsc. This is the default input mode.
//
// 2. Alt input mode. When ESC sequence is in the buffer and it doesn't match
// any known sequence. ESC enables ModAlt modifier for the next keyboard event.
//
// Both input modes can be OR'ed with Mouse mode. Setting Mouse mode bit up will
// enable mouse button click events.
//
// If 'mode' is InputCurrent, returns the current input mode. See also Input*
// constants.
func SetInputMode(mode InputMode) InputMode {
	if mode == InputCurrent {
		return input_mode
	}
	if mode&InputMouse != 0 {
		out.WriteString(funcs[t_enter_mouse])
	} else {
		out.WriteString(funcs[t_exit_mouse])
	}

	input_mode = mode
	return input_mode
}

// Sets the termbox output mode. Termbox has four output options:
// 1. OutputNormal => [1..8]
//    This mode provides 8 different colors:
//        black, red, green, yellow, blue, magenta, cyan, white
//    Shortcut: ColorBlack, ColorRed, ...
//    Attributes: AttrBold, AttrUnderline, AttrReverse
//
//    Example usage:
//        SetCell(x, y, '@', ColorBlack | AttrBold, ColorRed);
//
// 2. Output256 => [1..256]
//    In this mode you can leverage the 256 terminal mode:
//    0x00 - 0x07: the 8 colors as in OutputNormal
//    0x08 - 0x0f: Color* | AttrBold
//    0x10 - 0xe7: 216 different colors
//    0xe8 - 0xff: 24 different shades of grey
//
//    Example usage:
//        SetCell(x, y, '@', 184, 240);
//        SetCell(x, y, '@', 0xb8, 0xf0);
//
// 3. Output216 => [1..216]
//    This mode supports the 3rd range of the 256 mode only.
//    But you dont need to provide an offset.
//
// 4. OutputGrayscale => [1..24]
//    This mode supports the 4th range of the 256 mode only.
//    But you dont need to provide an offset.
//
// In all modes, 0 represents the default color.
//
// `go run _demos/output.go` to see its impact on your terminal.
//
// If 'mode' is OutputCurrent, it returns the current output mode.
//
// Note that this may return a different OutputMode than the one requested,
// as the requested mode may not be available on the target platform.
func SetOutputMode(mode OutputMode) OutputMode {
	if mode == OutputCurrent {
		return output_mode
	}

	output_mode = mode
	return output_mode
}

// Sync comes handy when something causes desync between termbox's understanding
// of a terminal buffer and the reality. Such as a third party process. Sync
// forces a complete resync between the termbox and a terminal, it may not be
// visually pretty though.
func Sync() error {
	front_buffer.clear()
	err := send_clear()
	if err != nil {
		return err
	}

	return Flush()
}
<|endoftext|>


<commit_before>package goprismic

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

type Api struct {
	URL         string
	AccessToken string
	Data        ApiData
}

type ApiData struct {
	Forms         map[string]Form   `json:"forms"`
	Refs          []Ref             `json:"refs"`
	Bookmarks     map[string]string `json:"bookmarks"`
	Tags          []string          `json:"tags"`
	Types         map[string]string `json:"types"`
	OAuthInitiate string            `json:"oauth_initiate"`
	OAuthToken    string            `json:"oauth_token"`
}

// Api entry point
func Get(u, accessToken string) (*Api, error) {
	api := &Api{AccessToken: accessToken, URL: u}
	api.Data.Refs = make([]Ref, 0, 128)
	err := api.call(api.URL, map[string]string{}, &api.Data)
	if err != nil {
		return nil, err
	}
	return api, nil
}

// Refreshes the Api data
func (a *Api) Refresh() error {
	return a.call(a.URL, map[string]string{}, &a.Data)
}

// Fetches the master ref
func (a *Api) Master() *SearchForm {
	for _, r := range a.Data.Refs {
		if r.IsMasterRef {
			return a.createSearchForm(r)
		}
	}
	return &SearchForm{err: fmt.Errorf("Master ref not found !?!")}
}

// Fetch another ref
func (a *Api) Ref(label string) *SearchForm {
	for _, r := range a.Data.Refs {
		if r.Label == label {
			return a.createSearchForm(r)
		}
	}
	return &SearchForm{err: fmt.Errorf("No ref found with label '%s'", label)}
}

func (a *Api) createSearchForm(r Ref) *SearchForm {
	f := &SearchForm{api: a, ref: r}
	f.data = make(map[string]string)
	return f
}

func (a *Api) call(u string, data map[string]string, res interface{}) error {
	callurl, errparse := url.Parse(u)
	if errparse != nil {
		return errparse
	}

	values := callurl.Query()
	for k, v := range data {
		values.Set(k, v)
	}
	if a.AccessToken != "" {
		values.Set("access_token", a.AccessToken)
	}
	callurl.RawQuery = values.Encode()

	req, errreq := http.NewRequest("GET", callurl.String(), nil)
	if errreq != nil {
		return errreq
	}
	req.Header.Add("Accept", "application/json")
	resp, errdo := http.DefaultClient.Do(req)
	defer resp.Body.Close()
	if errdo != nil {
		return errdo
	}
	encoded, errread := ioutil.ReadAll(resp.Body)
	if errread != nil {
		return errread
	}
	if resp.StatusCode != 200 {
		err := new(PrismicError)
		errjson := json.Unmarshal(encoded, err)
		if errjson != nil {
			return errjson
		} else {
			return err
		}
	}
	errjson := json.Unmarshal(encoded, res)
	return errjson
}
<commit_msg>close body only if the http request did not fail<commit_after>package goprismic

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

type Api struct {
	URL         string
	AccessToken string
	Data        ApiData
}

type ApiData struct {
	Forms         map[string]Form   `json:"forms"`
	Refs          []Ref             `json:"refs"`
	Bookmarks     map[string]string `json:"bookmarks"`
	Tags          []string          `json:"tags"`
	Types         map[string]string `json:"types"`
	OAuthInitiate string            `json:"oauth_initiate"`
	OAuthToken    string            `json:"oauth_token"`
}

// Api entry point
func Get(u, accessToken string) (*Api, error) {
	api := &Api{AccessToken: accessToken, URL: u}
	api.Data.Refs = make([]Ref, 0, 128)
	err := api.call(api.URL, map[string]string{}, &api.Data)
	if err != nil {
		return nil, err
	}
	return api, nil
}

// Refreshes the Api data
func (a *Api) Refresh() error {
	return a.call(a.URL, map[string]string{}, &a.Data)
}

// Fetches the master ref
func (a *Api) Master() *SearchForm {
	for _, r := range a.Data.Refs {
		if r.IsMasterRef {
			return a.createSearchForm(r)
		}
	}
	return &SearchForm{err: fmt.Errorf("Master ref not found !?!")}
}

// Fetch another ref
func (a *Api) Ref(label string) *SearchForm {
	for _, r := range a.Data.Refs {
		if r.Label == label {
			return a.createSearchForm(r)
		}
	}
	return &SearchForm{err: fmt.Errorf("No ref found with label '%s'", label)}
}

func (a *Api) createSearchForm(r Ref) *SearchForm {
	f := &SearchForm{api: a, ref: r}
	f.data = make(map[string]string)
	return f
}

func (a *Api) call(u string, data map[string]string, res interface{}) error {
	callurl, errparse := url.Parse(u)
	if errparse != nil {
		return errparse
	}

	values := callurl.Query()
	for k, v := range data {
		values.Set(k, v)
	}
	if a.AccessToken != "" {
		values.Set("access_token", a.AccessToken)
	}
	callurl.RawQuery = values.Encode()

	req, errreq := http.NewRequest("GET", callurl.String(), nil)
	if errreq != nil {
		return errreq
	}
	req.Header.Add("Accept", "application/json")
	resp, errdo := http.DefaultClient.Do(req)
	if errdo != nil {
		return errdo
	}
	defer resp.Body.Close()
	encoded, errread := ioutil.ReadAll(resp.Body)
	if errread != nil {
		return errread
	}
	if resp.StatusCode != 200 {
		err := new(PrismicError)
		errjson := json.Unmarshal(encoded, err)
		if errjson != nil {
			return errjson
		} else {
			return err
		}
	}
	errjson := json.Unmarshal(encoded, res)
	return errjson
}
<|endoftext|>


<commit_before>package main

import (
	"os"
	"fmt"
	"log"
	"time"
	"strconv"
 	"database/sql"
	"github.com/coopernurse/gorp"
	_ "github.com/mattn/go-sqlite3"
	"github.com/aviddiviner/gin-limit"
	"github.com/gin-gonic/gin"
)

var (
	dbFilename  string = "./db/electronicArtArgentina.sqlite3"
	tableName   string = "exhibiciones"
	httpPort    string = "8080"
	apiHostname string = "http://arte-electronico.cyberpunk.com.ar"
	apiPath     string = "/api"
	perPage     int = 20
	firstPage   string = "1"
	lastPage    string = "80" // (1593 / perPage) // TODO FIX ME

)

var dbmap = initDb()

type (
	Exhibition struct {
		Id         int64 `db:"id" json:"-"`
		Anio       int `db:"anio" json:"year"`
		Nombre     string `db:"nombre_exhibicion" json:"exhibition"`
		Locacion   string `db:"nombre_locacion" json:"location"`
		Curador0   string `db:"curador_0" json:"curator_0,omitempty"`
		Curador1   string `db:"curador_1" json:"curator_1,omitempty"`
		Curador2   string `db:"curador_2" json:"curator_2,omitempty"`
		Curador3   string `db:"curador_3" json:"curator_3,omitempty"`
		FechaIncio time.Time `db:"fecha_inicio" json:"start"`
		FechaFin   time.Time `db:"fecha_finalizacion" json:"end"`
		Obra       string `db:"nombre_obra" json:"work,omitempty"`
		Artista0   string `db:"nombre_artista_0" json:"artist_0,omitempty"`
		Artista1   string `db:"nombre_artista_1" json:"artist_1,omitempty"`
		Artista2   string `db:"nombre_artista_2" json:"artist_2,omitempty"`
		Artista3   string `db:"nombre_artista_3" json:"artist_3,omitempty"`
		Artista4   string `db:"nombre_artista_4" json:"artist_3,omitempty"`
		Artista5   string `db:"nombre_artista_5" json:"artist_5,omitempty"`
		Artista6   string `db:"nombre_artista_6" json:"artist_6,omitempty"`
		Artista7   string `db:"nombre_artista_7" json:"artist_7,omitempty"`
		Artista8   string `db:"nombre_artista_8" json:"artist_8,omitempty"`
		Tecnica    string `db:"tecnica" json:"technique,omitempty"`
	}

	PaginationLinks struct {
		First       string `json:"first"`
		Last       string `json:"last"`
		Prev       string `json:"prev"`
		Next       string `json:"next"`
	}

	ResourceObject struct {
		Id         int64 `json:"id"`
		Type       string `json:"type"`
		Attributes Exhibition `json:"attributes"`
	}

	ResourceObjects struct {
		Data []ResourceObject `json:"data"`
		Links PaginationLinks `json:"links"`
	}

	Error struct {
		Id     string `json:"id"`
		Status int `json:"status"`
		Title  string `json:"title"`
		Detail string `json:"detail"`
	}

)

func paginationLink(endpoint string, pg string, which string) string {
	page, err := strconv.ParseInt(pg, 10, 32)
	checkErr(err)
	first, ferr := strconv.ParseInt(firstPage, 10, 32)
	checkErr(ferr)
	last, lerr := strconv.ParseInt(lastPage, 10, 32)
	checkErr(lerr)

	switch which {
	case "prev":
		if page > first {
			page = page - 1
		}
	case "next":
		if page <= last {
			page = page + 1
		}
	}

	return fmt.Sprintf("%s/%s?page=%d", apiPath, endpoint, page)
}

func newResourceObject(attr Exhibition) ResourceObject {
    return ResourceObject {
        Id: attr.Id,
        Type: "exhibitions",
        Attributes: attr,
    }
}

func newResourceObjects (exhibitions []Exhibition, endpoint string, pg string) ResourceObjects {
	resources := make([]ResourceObject, 0)
	for e := range exhibitions {
		resources = append(resources, newResourceObject(exhibitions[e]))
	}

	links := PaginationLinks {
		First: paginationLink(endpoint, firstPage, ""),
		Last: paginationLink(endpoint, lastPage, ""),
		Prev: paginationLink(endpoint, pg, "prev"),
		Next: paginationLink(endpoint, pg, "next"),
	}

	return ResourceObjects{Data: resources, Links: links}
}

func pageOffset(pg string) int {
	page, err := strconv.Atoi(pg)
	if err != nil {
		page = 1
	}

	return perPage * (page - 1)
}

func PaginateQuery(query, pg string) string {
	return fmt.Sprintf("%s LIMIT %d OFFSET %d", query, perPage, pageOffset(pg))
}

func init() {
	p := os.Getenv("HTTP_PORT")
	if p != "" {
		httpPort = p
	}

	d := os.Getenv("DB_FILENAME")
	if d != "" {
		dbFilename = d
	}
}

func main() {
	router := gin.Default()
	router.Use(ApiCors())
	router.Use(limit.MaxAllowed(5))

	// API v1
	v1 := router.Group("api/v1")
	{
		v1.GET("/", ListEndpoints)  // list all exhibitions
		v1.GET("/exhibitions", GetExhibitions)  // list all exhibitions
		v1.GET("/exhibitions/:e_id", GetExhibition)  // show an exhibition
		v1.GET("/search", SearchExhibitions)  // search exhibitions

		v1.OPTIONS("/", EndpointsOptions) // http options
		v1.OPTIONS("/exhibitions", EndpointsOptions) // http options
		v1.OPTIONS("/exhibitions/:e_id", EndpointsOptions) // http options
		v1.OPTIONS("/search", EndpointsOptions) // http options
	}

	// API
	current := router.Group("api")
	{
		current.GET("/", ListEndpoints)  // list all exhibitions
		current.GET("/exhibitions", GetExhibitions)  // list all exhibitions
		current.GET("/exhibitions/:e_id", GetExhibition)  // show an exhibition
		current.GET("/search", SearchExhibitions)  // search exhibitions

		current.OPTIONS("/", EndpointsOptions) // http options
		current.OPTIONS("/exhibitions", EndpointsOptions) // http options
		current.OPTIONS("/exhibitions/:e_id", EndpointsOptions) // http options
		current.OPTIONS("/search", EndpointsOptions) // http options
	}

	// Home
	router.LoadHTMLGlob("tmpl/*")
	router.GET("/", func(c *gin.Context) {
		c.HTML(200, "index.tmpl", gin.H{
			"title": "JSON API for Dataset Arte Electrónico Argentino",
			"version": "v1",
		})
	})

	router.Run(fmt.Sprintf(":%s", httpPort))
}

func checkErr(err error) {
	if err != nil {
		log.Println(err)
	}
}

func initDb() *gorp.DbMap {
	db, err := sql.Open("sqlite3", dbFilename)
	checkErr(err)
	dbmap := &gorp.DbMap{Db: db, Dialect: gorp.SqliteDialect{}}
	dbmap.AddTableWithName(Exhibition{}, tableName).SetKeys(true, "Id")
	err = dbmap.CreateTablesIfNotExists()
	checkErr(err)
	return dbmap
}

func EndpointsOptions(c *gin.Context) {
	c.Writer.Header().Set("Access-Control-Allow-Methods", "OPTIONS, GET")
	c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type")
	c.Next()
}

func ApiCors() gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Writer.Header().Add("Access-Control-Allow-Origin", "*")
		c.Next()
	}
}

func ListEndpoints(c *gin.Context) {
	api_url := fmt.Sprintf("%s%s/", apiHostname, apiPath)
	c.JSON(200, gin.H{
		"exhibitions_url": fmt.Sprintf("%s%s{/exhibition_id}{?page}", api_url, "exhibitions"),
		"search_url": fmt.Sprintf("%s%s?q={query}{&year,when,technique,curator,artist,work,page}", api_url, "search"),
	})
}

// http http://localhost:8080/api/v1/exhibitions
func GetExhibitions(c *gin.Context) {
	var exhibitions []Exhibition
	page := c.DefaultQuery("page", "1")
	query := PaginateQuery("SELECT * FROM exhibiciones ORDER BY id", page)
	_, err := dbmap.Select(&exhibitions, query)
	checkErr(err)

	if err == nil {
		content := newResourceObjects(exhibitions, "exhibitions", page)
		c.JSON(200, content)
	} else {
		jsonErr := &Error{"not_found", 404, "Not Found Error", "No exhibition found."}
		c.JSON(404, jsonErr)
	}
}

// http http://localhost:8080/api/v1/exhibitions/1
func GetExhibition(c *gin.Context) {
	id := c.Params.ByName("e_id")
	var exhibition Exhibition
	err := dbmap.SelectOne(&exhibition, "SELECT * FROM exhibiciones WHERE id=? LIMIT 1", id)
	checkErr(err)

	if err == nil {
		content := newResourceObject(exhibition)
		c.JSON(200, content)
	} else {
		jsonErr := &Error{"not_found", 404, "Not Found Error", "Your query didn't match any exhibition."}
		c.JSON(404, jsonErr)
	}
}

// http http://localhost:8080/api/v1/search?q=festival[&year=1998[&when=since[&technique=videoarte[&artist=rytel[&curator=taquini[&work=sheep]]]]]]
func SearchExhibitions(c *gin.Context) {
	var exhibitions []Exhibition
	q := c.Query("q")
	year := c.Query("year")
	when := c.Query("when")
	where := c.Query("where")
	curator := c.Query("curator")
	artist := c.Query("artist")
	technique := c.Query("technique")
	work := c.Query("work")
	page := c.DefaultQuery("page", "1")

	and_year := ""
	if year != "" {
		y, yerr := strconv.ParseInt(year, 10, 32)
		checkErr(yerr)

		cmp := "="
		switch when {
		case "until", "UNTIL":
			cmp = "<="
		case "since", "SINCE":
			cmp = ">="
		}

		and_year = fmt.Sprintf("AND anio %s %d", cmp, y)
	}

	and_where := ""
	if where != "" {
		and_where = fmt.Sprintf("AND nombre_locacion LIKE '%%%s%%'", where)
	}

	and_work := ""
	if work != "" {
		and_work = fmt.Sprintf("AND nombre_obra LIKE '%%%s%%'", work)
	}

	and_technique := ""
	if technique != "" {
		and_technique = fmt.Sprintf("AND tecnica LIKE '%%%s%%'", technique)
	}

	and_curator := ""
	if curator != "" {
		and_curator = fmt.Sprintf("AND curadores  MATCH '*%s*'", curator)
	}

	and_artist := ""
	if artist != "" {
		and_artist = fmt.Sprintf("AND artistas MATCH '*%s*'", artist)
	}

	query := fmt.Sprintf(`SELECT *
FROM exhibiciones
NATURAL JOIN artistas
NATURAL JOIN curadores
WHERE nombre_exhibicion LIKE '%%%s%%'
%s %s %s %s %s %s`, q,
		and_year, and_where, and_technique, and_work, and_artist, and_curator)

	query = PaginateQuery(query, page)
	_, err := dbmap.Select(&exhibitions, query)
	checkErr(err)

	if err == nil {
		content := newResourceObjects(exhibitions, "search", page)
		c.JSON(200, content)
	} else {
		jsonErr := &Error{"not_found", 404, "Not Found Error", "Your query didn't match any exhibition."}
		c.JSON(404, jsonErr)
	}
}
<commit_msg>last page when searching is empty. not the best solution, tho.<commit_after>package main

import (
	"os"
	"fmt"
	"log"
	"time"
	"strconv"
 	"database/sql"
	"github.com/coopernurse/gorp"
	_ "github.com/mattn/go-sqlite3"
	"github.com/aviddiviner/gin-limit"
	"github.com/gin-gonic/gin"
)

var (
	dbFilename  string = "./db/electronicArtArgentina.sqlite3"
	tableName   string = "exhibiciones"
	httpPort    string = "8080"
	apiHostname string = "http://arte-electronico.cyberpunk.com.ar"
	apiPath     string = "/api"
	perPage     int = 20
	firstPage   string = "1"
	lastPage    string = "80" // (1593 / perPage) // TODO FIX ME

)

var dbmap = initDb()

type (
	Exhibition struct {
		Id         int64 `db:"id" json:"-"`
		Anio       int `db:"anio" json:"year"`
		Nombre     string `db:"nombre_exhibicion" json:"exhibition"`
		Locacion   string `db:"nombre_locacion" json:"location"`
		Curador0   string `db:"curador_0" json:"curator_0,omitempty"`
		Curador1   string `db:"curador_1" json:"curator_1,omitempty"`
		Curador2   string `db:"curador_2" json:"curator_2,omitempty"`
		Curador3   string `db:"curador_3" json:"curator_3,omitempty"`
		FechaIncio time.Time `db:"fecha_inicio" json:"start"`
		FechaFin   time.Time `db:"fecha_finalizacion" json:"end"`
		Obra       string `db:"nombre_obra" json:"work,omitempty"`
		Artista0   string `db:"nombre_artista_0" json:"artist_0,omitempty"`
		Artista1   string `db:"nombre_artista_1" json:"artist_1,omitempty"`
		Artista2   string `db:"nombre_artista_2" json:"artist_2,omitempty"`
		Artista3   string `db:"nombre_artista_3" json:"artist_3,omitempty"`
		Artista4   string `db:"nombre_artista_4" json:"artist_3,omitempty"`
		Artista5   string `db:"nombre_artista_5" json:"artist_5,omitempty"`
		Artista6   string `db:"nombre_artista_6" json:"artist_6,omitempty"`
		Artista7   string `db:"nombre_artista_7" json:"artist_7,omitempty"`
		Artista8   string `db:"nombre_artista_8" json:"artist_8,omitempty"`
		Tecnica    string `db:"tecnica" json:"technique,omitempty"`
	}

	PaginationLinks struct {
		First       string `json:"first"`
		Last       string `json:"last"`
		Prev       string `json:"prev"`
		Next       string `json:"next"`
	}

	ResourceObject struct {
		Id         int64 `json:"id"`
		Type       string `json:"type"`
		Attributes Exhibition `json:"attributes"`
	}

	ResourceObjects struct {
		Data []ResourceObject `json:"data"`
		Links PaginationLinks `json:"links"`
	}

	Error struct {
		Id     string `json:"id"`
		Status int `json:"status"`
		Title  string `json:"title"`
		Detail string `json:"detail"`
	}

)

func paginationLink(endpoint string, pg string, which string) string {
	page, err := strconv.ParseInt(pg, 10, 32)
	checkErr(err)
	first, ferr := strconv.ParseInt(firstPage, 10, 32)
	checkErr(ferr)
	last, lerr := strconv.ParseInt(lastPage, 10, 32)
	checkErr(lerr)

	switch which {
	case "prev":
		if page > first {
			page = page - 1
		}
	case "next":
		if page <= last {
			page = page + 1
		}
	}

	link := fmt.Sprintf("%s/%s?page=%d", apiPath, endpoint, page)
	if endpoint == "search" && which == "last" { // TODO ugly
		link = ""
	}

	return link
}

func newResourceObject(attr Exhibition) ResourceObject {
    return ResourceObject {
        Id: attr.Id,
        Type: "exhibitions",
        Attributes: attr,
    }
}

func newResourceObjects (exhibitions []Exhibition, endpoint string, pg string) ResourceObjects {
	resources := make([]ResourceObject, 0)
	for e := range exhibitions {
		resources = append(resources, newResourceObject(exhibitions[e]))
	}

	links := PaginationLinks {
		First: paginationLink(endpoint, firstPage, "first"),
		Last: paginationLink(endpoint, lastPage, "last"),
		Prev: paginationLink(endpoint, pg, "prev"),
		Next: paginationLink(endpoint, pg, "next"),
	}

	return ResourceObjects{Data: resources, Links: links}
}

func pageOffset(pg string) int {
	page, err := strconv.Atoi(pg)
	if err != nil {
		page = 1
	}

	return perPage * (page - 1)
}

func PaginateQuery(query, pg string) string {
	return fmt.Sprintf("%s LIMIT %d OFFSET %d", query, perPage, pageOffset(pg))
}

func init() {
	p := os.Getenv("HTTP_PORT")
	if p != "" {
		httpPort = p
	}

	d := os.Getenv("DB_FILENAME")
	if d != "" {
		dbFilename = d
	}
}

func main() {
	router := gin.Default()
	router.Use(ApiCors())
	router.Use(limit.MaxAllowed(5))

	// API v1
	v1 := router.Group("api/v1")
	{
		v1.GET("/", ListEndpoints)  // list all exhibitions
		v1.GET("/exhibitions", GetExhibitions)  // list all exhibitions
		v1.GET("/exhibitions/:e_id", GetExhibition)  // show an exhibition
		v1.GET("/search", SearchExhibitions)  // search exhibitions

		v1.OPTIONS("/", EndpointsOptions) // http options
		v1.OPTIONS("/exhibitions", EndpointsOptions) // http options
		v1.OPTIONS("/exhibitions/:e_id", EndpointsOptions) // http options
		v1.OPTIONS("/search", EndpointsOptions) // http options
	}

	// API -- TODO improve this
	current := router.Group("api")
	{
		current.GET("/", ListEndpoints)  // list all exhibitions
		current.GET("/exhibitions", GetExhibitions)  // list all exhibitions
		current.GET("/exhibitions/:e_id", GetExhibition)  // show an exhibition
		current.GET("/search", SearchExhibitions)  // search exhibitions

		current.OPTIONS("/", EndpointsOptions) // http options
		current.OPTIONS("/exhibitions", EndpointsOptions) // http options
		current.OPTIONS("/exhibitions/:e_id", EndpointsOptions) // http options
		current.OPTIONS("/search", EndpointsOptions) // http options
	}

	// Home
	router.LoadHTMLGlob("tmpl/*")
	router.GET("/", func(c *gin.Context) {
		c.HTML(200, "index.tmpl", gin.H{
			"title": "JSON API for Dataset Arte Electrónico Argentino",
			"version": "v1",
		})
	})

	router.Run(fmt.Sprintf(":%s", httpPort))
}

func checkErr(err error) {
	if err != nil {
		log.Println(err)
	}
}

func initDb() *gorp.DbMap {
	db, err := sql.Open("sqlite3", dbFilename)
	checkErr(err)
	dbmap := &gorp.DbMap{Db: db, Dialect: gorp.SqliteDialect{}}
	dbmap.AddTableWithName(Exhibition{}, tableName).SetKeys(true, "Id")
	err = dbmap.CreateTablesIfNotExists()
	checkErr(err)
	return dbmap
}

func EndpointsOptions(c *gin.Context) {
	c.Writer.Header().Set("Access-Control-Allow-Methods", "OPTIONS, GET")
	c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type")
	c.Next()
}

func ApiCors() gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Writer.Header().Add("Access-Control-Allow-Origin", "*")
		c.Next()
	}
}

func ListEndpoints(c *gin.Context) {
	api_url := fmt.Sprintf("%s%s/", apiHostname, apiPath)
	c.JSON(200, gin.H{
		"exhibitions_url": fmt.Sprintf("%s%s{/exhibition_id}{?page}", api_url, "exhibitions"),
		"search_url": fmt.Sprintf("%s%s?q={query}{&year,when,technique,curator,artist,work,page}", api_url, "search"),
	})
}

// http http://localhost:8080/api/v1/exhibitions
func GetExhibitions(c *gin.Context) {
	var exhibitions []Exhibition
	page := c.DefaultQuery("page", "1")
	query := PaginateQuery("SELECT * FROM exhibiciones ORDER BY id", page)
	_, err := dbmap.Select(&exhibitions, query)
	checkErr(err)

	if err == nil {
		content := newResourceObjects(exhibitions, "exhibitions", page)
		c.JSON(200, content)
	} else {
		jsonErr := &Error{"not_found", 404, "Not Found Error", "No exhibition found."}
		c.JSON(404, jsonErr)
	}
}

// http http://localhost:8080/api/v1/exhibitions/1
func GetExhibition(c *gin.Context) {
	id := c.Params.ByName("e_id")
	var exhibition Exhibition
	err := dbmap.SelectOne(&exhibition, "SELECT * FROM exhibiciones WHERE id=? LIMIT 1", id)
	checkErr(err)

	if err == nil {
		content := newResourceObject(exhibition)
		c.JSON(200, content)
	} else {
		jsonErr := &Error{"not_found", 404, "Not Found Error", "Your query didn't match any exhibition."}
		c.JSON(404, jsonErr)
	}
}

// http http://localhost:8080/api/v1/search?q=festival[&year=1998[&when=since[&technique=videoarte[&artist=rytel[&curator=taquini[&work=sheep]]]]]]
func SearchExhibitions(c *gin.Context) {
	var exhibitions []Exhibition
	q := c.Query("q")
	year := c.Query("year")
	when := c.Query("when")
	where := c.Query("where")
	curator := c.Query("curator")
	artist := c.Query("artist")
	technique := c.Query("technique")
	work := c.Query("work")
	page := c.DefaultQuery("page", "1")

	and_year := ""
	if year != "" {
		y, yerr := strconv.ParseInt(year, 10, 32)
		checkErr(yerr)

		cmp := "="
		switch when {
		case "until", "UNTIL":
			cmp = "<="
		case "since", "SINCE":
			cmp = ">="
		}

		and_year = fmt.Sprintf("AND anio %s %d", cmp, y)
	}

	and_where := ""
	if where != "" {
		and_where = fmt.Sprintf("AND nombre_locacion LIKE '%%%s%%'", where)
	}

	and_work := ""
	if work != "" {
		and_work = fmt.Sprintf("AND nombre_obra LIKE '%%%s%%'", work)
	}

	and_technique := ""
	if technique != "" {
		and_technique = fmt.Sprintf("AND tecnica LIKE '%%%s%%'", technique)
	}

	and_curator := ""
	if curator != "" {
		and_curator = fmt.Sprintf("AND curadores  MATCH '*%s*'", curator)
	}

	and_artist := ""
	if artist != "" {
		and_artist = fmt.Sprintf("AND artistas MATCH '*%s*'", artist)
	}

	query := fmt.Sprintf(`SELECT *
FROM exhibiciones
NATURAL JOIN artistas
NATURAL JOIN curadores
WHERE nombre_exhibicion LIKE '%%%s%%'
%s %s %s %s %s %s`, q,
		and_year, and_where, and_technique, and_work, and_artist, and_curator)

	query = PaginateQuery(query, page)
	_, err := dbmap.Select(&exhibitions, query)
	checkErr(err)

	if err == nil {
		content := newResourceObjects(exhibitions, "search", page)
		c.JSON(200, content)
	} else {
		jsonErr := &Error{"not_found", 404, "Not Found Error", "Your query didn't match any exhibition."}
		c.JSON(404, jsonErr)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"database/sql"
	"encoding/json"
	"errors"
	"fmt"
	"time"
	"github.com/hoisie/web"
	"io/ioutil"
	"strconv"
)

var allowed_speeds = []int{40, 60, 80, 100, 120}

func contains(a int, list []int) bool {
	for _, b := range list {
		if b == a {
			return true
		}
	}
	return false
}

func check_coordinate_sanity(matrix []Coord, country string) (bool, error) {
	bbox := bounding_boxes[country]

	verify := func(pair []float64) bool {
		lat, long := pair[0], pair[1]
		if long > bbox["long_max"] || long < bbox["long_min"] || lat > bbox["lat_max"] || lat < bbox["lat_min"] {
			return false
		}
		return true
	}

	for i, pair := range matrix {
		res := verify(pair)
		if !res {
			return false, errors.New(fmt.Sprintf("Coordinate (lat: %f, long: %f) at matrix index %d is outside the limits for country \"%s\" which is %vi. Make sure you use [[LAT, LONG]...].", pair[0], pair[1], i, country, bbox))
		}
	}
	return true, nil
}

// Starts a computation, validates the matrix in POST.
// If matrix data is missing, returns 400 Bad Request.
// If on the other hand matrix is data is not missing,
// but makes no sense, it returns 422 Unprocessable Entity.
func (server *Server) PostMatrix(ctx *web.Context) {
	var hash string
	var computed bool
	params := []string{"matrix", "speed_profile", "country"}

	body := ctx.Request.Body

	var buf bytes.Buffer
	buf.ReadFrom(body)
	bodyParams := buf.Bytes()
	debug.Println(buf.String())
	var paramBlob map[string]interface{}
	// Parse params
	json.Unmarshal(bodyParams, &paramBlob)

	ok := false
	for _, param := range params {
		// ok will be set to false if ctx.Params doesn't contain param
		_, ok = paramBlob[param]
	}

	if ok {
		data := paramBlob["matrix"].(string)
		country := paramBlob["country"].(string)
		sp, jep := paramBlob["speed_profile"].(float64)

		speed_profile := int(sp)
		// Sanitize speed profile.
		if !jep || !contains(speed_profile, allowed_speeds) {
			msg := fmt.Sprintf("speed profile '%d' makes no sense, must be one of %s", speed_profile, fmt.Sprint(allowed_speeds))
			ctx.Abort(422, msg)
			return
		}
		// Sanitize country.
		if _, ok := server.AllowedCountries[country]; !ok {
			countries := ""
			for k := range server.AllowedCountries {
				countries += k + " "
			}
			ctx.Abort(422, "country "+country+" not allowed, must be one of: "+countries)
			return
		}

		mat, err := parse_json_matrix(data)
		if err != nil {
			ctx.Abort(422, err.Error())
			return
		}

		ok, err = check_coordinate_sanity(mat, country)
		if err != nil {
			ctx.Abort(422, err.Error())
			return
		}

		hash, computed = server.CreateMatrixComputation(mat, country, int(speed_profile))

		// launch computation here if the result wasn't proxied.
		if !computed {
			go server.ComputeMatrix(hash)
		}
	} else {
		ctx.Abort(400, "Missing matrix data or speed profile or country. You sent: "+buf.String())
		return
	}

	loc := fmt.Sprintf("/spp/%s", hash)

	ctx.Redirect(201, loc)
}

// Returns a computation from the server as identified by the resource parameter
// in GET.
func (server *Server) GetMatrix(ctx *web.Context, resource string) string {
	progress, err := server.GetMatrixComputationProgress(resource)
	if err != nil {
		ctx.Abort(500, err.Error())
		return ""
	}

	if progress == "complete" {
		url := fmt.Sprintf("/spp/%s/result", resource)
		debug.Println("redirect ->", url)
		ctx.Redirect(303, url)
	} else {
		ctx.ContentType("json")
		msg := fmt.Sprintf(`{ "progress": "%s" }`, progress)
		return msg
	}

	return ""
}

func (server *Server) GetMatrixResult(ctx *web.Context, resource string) string {
	if ex, _ := server.client.Exists(resource); !ex {
		ctx.Abort(500, "Result expired. POST again.")
		return ""
	}

	progress, err := server.GetMatrixComputationProgress(resource)
	if progress != "complete" {
		ctx.Abort(403, "Computation is not ready yet.")
		return ""
	}

	if exists, _ := server.client.Hexists(resource, "see"); exists {
		debug.Println("Got proxy result")
		pointer, _ := server.client.Hget(resource, "see")
		resource = string(pointer)
	}

	data, err := server.client.Hget(resource, "result")
	if err != nil {
		ctx.Abort(500, "Redis error: "+err.Error())
		return ""
	}

	if data != nil {
		ctx.ContentType("json")
		return fmt.Sprintf("{ \"Matrix\": %s }", string(data))
	}

	return ""
}

func (server *Server) GetServerStatus(ctx *web.Context) string {
	db, _ := sql.Open("postgres", server.Config.String())
	defer db.Close()

	err := db.Ping()

	if err != nil {
		ctx.Abort(500, "Could not connect to database: "+err.Error())
		return ""
	}

	return "OK"
}

func (server *Server) GetCorrectCoordinate(ctx *web.Context) string {
	s_lat, lat_ok := ctx.Params["lat"]
	s_long, long_ok := ctx.Params["long"]

	// TODO(ane): implement automatic lookup of country
	s_country, country_ok := ctx.Params["country"]

	if !lat_ok || !long_ok || !country_ok {
		ctx.Abort(400, fmt.Sprintf("Missing parameter, need lat, long, country, you gave: %q", ctx.Params))
		return ""
	}

	lat, err := strconv.ParseFloat(s_lat, 32)
	if err != nil {
		ctx.Abort(400, fmt.Sprintf("Latitude %s is invalid, cannot parse!", s_lat))
	}

	long, err := strconv.ParseFloat(s_long, 32)
	if err != nil {
		ctx.Abort(400, fmt.Sprintf("Longitude %s is invalid, cannot parse!", s_long))
	}

	if _, ok := server.Config.AllowedCountries[s_country]; !ok {
		ctx.Abort(500, fmt.Sprintf("Country %s not allowed", s_country))
	}

	coord := Coord{lat, long}

	corr_node, err := CorrectPoint(server.Config, coord, s_country)
	if err != nil {
		ctx.Abort(500, err.Error())
	}

	response, err := json.Marshal(corr_node)
	if err != nil {
		ctx.Abort(500, err.Error())
	}

	ctx.Header().Set("Access-Control-Allow-Origin", "*")
	ctx.ContentType("application/json")
	return string(response)
}

func (server *Server) GetNodesToCoordinates(ctx *web.Context) string {
	nodes, ex := ctx.Params["nodes"]
	country, ex2 := ctx.Params["country"]

	if !ex || !ex2 {
		ctx.Abort(400, fmt.Sprintf("Missing parameter: either nodes or country is missing"))
		return ""
	}

	var parsedNodes []int
	if err := json.Unmarshal([]byte(nodes), &parsedNodes); err != nil {
		ctx.Abort(400, err.Error())
		return ""
	}

	coordinates, err := GetCoordinates(server.Config, country, parsedNodes)
	if err != nil {
		ctx.Abort(500, err.Error())
		return ""
	}

	cont, err := json.Marshal(coordinates)
	if err != nil {
		ctx.Abort(500, err.Error())
		return ""
	}

	ctx.Header().Set("Access-Control-Allow-Origin", "*")
	ctx.ContentType("application/json")
	return string(cont)
}

func (server *Server) GetPath(ctx *web.Context) string {
	p_source, p_source_ok := ctx.Params["source"]
	p_target, p_target_ok := ctx.Params["target"]
	p_country, p_country_ok := ctx.Params["country"]
	p_sp, p_sp_ok := ctx.Params["speed_profile"]

	if !p_source_ok || !p_target_ok || !p_country_ok || !p_sp_ok {
		ctx.Abort(400, fmt.Sprintf("Missing parameter, need country, speed_profile, source, target; you gave: %q", ctx.Params))
		return ""
	}

	if _, ok := server.Config.AllowedCountries[p_country]; !ok {
		ctx.Abort(500, fmt.Sprintf("Country %s not allowed", p_country))
		return ""
	}

	source, _ := strconv.Atoi(p_source)
	target, _ := strconv.Atoi(p_target)
	sp, _ := strconv.Atoi(p_sp)

	path, err := CalculatePath(source, target, p_country, sp)
	if err != nil {
		ctx.Abort(500, err.Error())
		return ""
	}

	data, err := json.Marshal(path)
	if err != nil {
		ctx.Abort(500, "json error: "+err.Error())
		return ""
	}

	ctx.Header().Set("Access-Control-Allow-Origin", "*")
	ctx.ContentType("application/json")
	return string(data)
}

func (server *Server) GetCoordinatePath(ctx *web.Context) string {
	p_source, p_source_ok := ctx.Params["source"]
	p_target, p_target_ok := ctx.Params["target"]
	p_sp, p_sp_ok := ctx.Params["speed_profile"]

	if !p_source_ok || !p_target_ok || !p_sp_ok {
		ctx.Abort(400, fmt.Sprintf("Missing parameter, need speed_profile, source, target; you gave: %q", ctx.Params))
		return ""
	}

	var sourceAddr, targetAddr Location
	if err := json.Unmarshal([]byte(p_source), &sourceAddr); err != nil {
		ctx.Abort(400, err.Error())
		return ""
	}

	if err := json.Unmarshal([]byte(p_target), &targetAddr); err != nil {
		ctx.Abort(400, err.Error())
		return ""
	}

	sp, _ := strconv.Atoi(p_sp)

	result, err := CalculateCoordinatePathFromAddresses(server.Config, sourceAddr, targetAddr, sp)
	if err != nil {
		ctx.Abort(500, err.Error())
		return ""
	}

	jsonResult, err := json.Marshal(result)
	if err != nil {
		ctx.Abort(500, err.Error())
		return ""
	}

	ctx.Header().Set("Access-Control-Allow-Origin", "*")
	ctx.ContentType("application/json")
	return string(jsonResult)
}

func (server *Server) PostResolve(ctx *web.Context) string {
	content, err := ioutil.ReadAll(ctx.Request.Body)
	var locations, resolvedLocations []Location

	// Parse params
	t := time.Now()
	if err := json.Unmarshal(content, &locations); err != nil {
		ctx.Abort(400, err.Error())
		return ""
	} else {
		for i := 0; i < len(locations); i++ {
			newLoc, err := ResolveLocation(server.Config, locations[i])
			if err != nil {
				ctx.Abort(422, err.Error())
				return ""
			}
			resolvedLocations = append(resolvedLocations, newLoc)
		}
	}

	e := time.Since(t)

	fmt.Printf("resolved %d locations in %s\n", len(resolvedLocations), e)
	res, err := json.Marshal(resolvedLocations)
	if err != nil {
		ctx.Abort(500, err.Error())
		return ""
	}

	ctx.Header().Set("Access-Control-Allow-Origin", "*")
	ctx.ContentType("application/json")
	return string(res)
}
<commit_msg>Add time.<commit_after>package main

import (
	"bytes"
	"database/sql"
	"encoding/json"
	"errors"
	"fmt"
	"time"
	"github.com/hoisie/web"
	"io/ioutil"
	"strconv"
)

var allowed_speeds = []int{40, 60, 80, 100, 120}

func contains(a int, list []int) bool {
	for _, b := range list {
		if b == a {
			return true
		}
	}
	return false
}

func check_coordinate_sanity(matrix []Coord, country string) (bool, error) {
	bbox := bounding_boxes[country]

	verify := func(pair []float64) bool {
		lat, long := pair[0], pair[1]
		if long > bbox["long_max"] || long < bbox["long_min"] || lat > bbox["lat_max"] || lat < bbox["lat_min"] {
			return false
		}
		return true
	}

	for i, pair := range matrix {
		res := verify(pair)
		if !res {
			return false, errors.New(fmt.Sprintf("Coordinate (lat: %f, long: %f) at matrix index %d is outside the limits for country \"%s\" which is %vi. Make sure you use [[LAT, LONG]...].", pair[0], pair[1], i, country, bbox))
		}
	}
	return true, nil
}

// Starts a computation, validates the matrix in POST.
// If matrix data is missing, returns 400 Bad Request.
// If on the other hand matrix is data is not missing,
// but makes no sense, it returns 422 Unprocessable Entity.
func (server *Server) PostMatrix(ctx *web.Context) {
	var hash string
	var computed bool
	params := []string{"matrix", "speed_profile", "country"}

	body := ctx.Request.Body

	var buf bytes.Buffer
	buf.ReadFrom(body)
	bodyParams := buf.Bytes()
	debug.Println(buf.String())
	var paramBlob map[string]interface{}
	// Parse params
	json.Unmarshal(bodyParams, &paramBlob)

	ok := false
	for _, param := range params {
		// ok will be set to false if ctx.Params doesn't contain param
		_, ok = paramBlob[param]
	}

	if ok {
		data := paramBlob["matrix"].(string)
		country := paramBlob["country"].(string)
		sp, jep := paramBlob["speed_profile"].(float64)

		speed_profile := int(sp)
		// Sanitize speed profile.
		if !jep || !contains(speed_profile, allowed_speeds) {
			msg := fmt.Sprintf("speed profile '%d' makes no sense, must be one of %s", speed_profile, fmt.Sprint(allowed_speeds))
			ctx.Abort(422, msg)
			return
		}
		// Sanitize country.
		if _, ok := server.AllowedCountries[country]; !ok {
			countries := ""
			for k := range server.AllowedCountries {
				countries += k + " "
			}
			ctx.Abort(422, "country "+country+" not allowed, must be one of: "+countries)
			return
		}

		mat, err := parse_json_matrix(data)
		if err != nil {
			ctx.Abort(422, err.Error())
			return
		}

		ok, err = check_coordinate_sanity(mat, country)
		if err != nil {
			ctx.Abort(422, err.Error())
			return
		}

		hash, computed = server.CreateMatrixComputation(mat, country, int(speed_profile))

		// launch computation here if the result wasn't proxied.
		if !computed {
			go server.ComputeMatrix(hash)
		}
	} else {
		ctx.Abort(400, "Missing matrix data or speed profile or country. You sent: "+buf.String())
		return
	}

	loc := fmt.Sprintf("/spp/%s", hash)

	ctx.Redirect(201, loc)
}

// Returns a computation from the server as identified by the resource parameter
// in GET.
func (server *Server) GetMatrix(ctx *web.Context, resource string) string {
	progress, err := server.GetMatrixComputationProgress(resource)
	if err != nil {
		ctx.Abort(500, err.Error())
		return ""
	}

	if progress == "complete" {
		url := fmt.Sprintf("/spp/%s/result", resource)
		debug.Println("redirect ->", url)
		ctx.Redirect(303, url)
	} else {
		ctx.ContentType("json")
		msg := fmt.Sprintf(`{ "progress": "%s" }`, progress)
		return msg
	}

	return ""
}

func (server *Server) GetMatrixResult(ctx *web.Context, resource string) string {
	if ex, _ := server.client.Exists(resource); !ex {
		ctx.Abort(500, "Result expired. POST again.")
		return ""
	}

	progress, err := server.GetMatrixComputationProgress(resource)
	if progress != "complete" {
		ctx.Abort(403, "Computation is not ready yet.")
		return ""
	}

	if exists, _ := server.client.Hexists(resource, "see"); exists {
		debug.Println("Got proxy result")
		pointer, _ := server.client.Hget(resource, "see")
		resource = string(pointer)
	}

	data, err := server.client.Hget(resource, "result")
	if err != nil {
		ctx.Abort(500, "Redis error: "+err.Error())
		return ""
	}

	if data != nil {
		ctx.ContentType("json")
		return fmt.Sprintf("{ \"Matrix\": %s }", string(data))
	}

	return ""
}

func (server *Server) GetServerStatus(ctx *web.Context) string {
	db, _ := sql.Open("postgres", server.Config.String())
	defer db.Close()

	err := db.Ping()

	if err != nil {
		ctx.Abort(500, "Could not connect to database: "+err.Error())
		return ""
	}

	return "OK"
}

func (server *Server) GetCorrectCoordinate(ctx *web.Context) string {
	s_lat, lat_ok := ctx.Params["lat"]
	s_long, long_ok := ctx.Params["long"]

	// TODO(ane): implement automatic lookup of country
	s_country, country_ok := ctx.Params["country"]

	if !lat_ok || !long_ok || !country_ok {
		ctx.Abort(400, fmt.Sprintf("Missing parameter, need lat, long, country, you gave: %q", ctx.Params))
		return ""
	}

	lat, err := strconv.ParseFloat(s_lat, 32)
	if err != nil {
		ctx.Abort(400, fmt.Sprintf("Latitude %s is invalid, cannot parse!", s_lat))
	}

	long, err := strconv.ParseFloat(s_long, 32)
	if err != nil {
		ctx.Abort(400, fmt.Sprintf("Longitude %s is invalid, cannot parse!", s_long))
	}

	if _, ok := server.Config.AllowedCountries[s_country]; !ok {
		ctx.Abort(500, fmt.Sprintf("Country %s not allowed", s_country))
	}

	coord := Coord{lat, long}

	corr_node, err := CorrectPoint(server.Config, coord, s_country)
	if err != nil {
		ctx.Abort(500, err.Error())
	}

	response, err := json.Marshal(corr_node)
	if err != nil {
		ctx.Abort(500, err.Error())
	}

	ctx.Header().Set("Access-Control-Allow-Origin", "*")
	ctx.ContentType("application/json")
	return string(response)
}

func (server *Server) GetNodesToCoordinates(ctx *web.Context) string {
	nodes, ex := ctx.Params["nodes"]
	country, ex2 := ctx.Params["country"]

	if !ex || !ex2 {
		ctx.Abort(400, fmt.Sprintf("Missing parameter: either nodes or country is missing"))
		return ""
	}

	var parsedNodes []int
	if err := json.Unmarshal([]byte(nodes), &parsedNodes); err != nil {
		ctx.Abort(400, err.Error())
		return ""
	}

	coordinates, err := GetCoordinates(server.Config, country, parsedNodes)
	if err != nil {
		ctx.Abort(500, err.Error())
		return ""
	}

	cont, err := json.Marshal(coordinates)
	if err != nil {
		ctx.Abort(500, err.Error())
		return ""
	}

	ctx.Header().Set("Access-Control-Allow-Origin", "*")
	ctx.ContentType("application/json")
	return string(cont)
}

func (server *Server) GetPath(ctx *web.Context) string {
	p_source, p_source_ok := ctx.Params["source"]
	p_target, p_target_ok := ctx.Params["target"]
	p_country, p_country_ok := ctx.Params["country"]
	p_sp, p_sp_ok := ctx.Params["speed_profile"]

	if !p_source_ok || !p_target_ok || !p_country_ok || !p_sp_ok {
		ctx.Abort(400, fmt.Sprintf("Missing parameter, need country, speed_profile, source, target; you gave: %q", ctx.Params))
		return ""
	}

	if _, ok := server.Config.AllowedCountries[p_country]; !ok {
		ctx.Abort(500, fmt.Sprintf("Country %s not allowed", p_country))
		return ""
	}

	source, _ := strconv.Atoi(p_source)
	target, _ := strconv.Atoi(p_target)
	sp, _ := strconv.Atoi(p_sp)

	path, err := CalculatePath(source, target, p_country, sp)
	if err != nil {
		ctx.Abort(500, err.Error())
		return ""
	}

	data, err := json.Marshal(path)
	if err != nil {
		ctx.Abort(500, "json error: "+err.Error())
		return ""
	}

	ctx.Header().Set("Access-Control-Allow-Origin", "*")
	ctx.ContentType("application/json")
	return string(data)
}

func (server *Server) GetCoordinatePath(ctx *web.Context) string {
	p_source, p_source_ok := ctx.Params["source"]
	p_target, p_target_ok := ctx.Params["target"]
	p_sp, p_sp_ok := ctx.Params["speed_profile"]

	if !p_source_ok || !p_target_ok || !p_sp_ok {
		ctx.Abort(400, fmt.Sprintf("Missing parameter, need speed_profile, source, target; you gave: %q", ctx.Params))
		return ""
	}

	var sourceAddr, targetAddr Location
	if err := json.Unmarshal([]byte(p_source), &sourceAddr); err != nil {
		ctx.Abort(400, err.Error())
		return ""
	}

	if err := json.Unmarshal([]byte(p_target), &targetAddr); err != nil {
		ctx.Abort(400, err.Error())
		return ""
	}

	sp, _ := strconv.Atoi(p_sp)

	result, err := CalculateCoordinatePathFromAddresses(server.Config, sourceAddr, targetAddr, sp)
	if err != nil {
		ctx.Abort(500, err.Error())
		return ""
	}

	jsonResult, err := json.Marshal(result)
	if err != nil {
		ctx.Abort(500, err.Error())
		return ""
	}

	ctx.Header().Set("Access-Control-Allow-Origin", "*")
	ctx.ContentType("application/json")
	return string(jsonResult)
}

func (server *Server) PostResolve(ctx *web.Context) string {
	content, err := ioutil.ReadAll(ctx.Request.Body)
	var locations, resolvedLocations []Location

	// Parse params
	t := time.Now()
	if err := json.Unmarshal(content, &locations); err != nil {
		ctx.Abort(400, "Couldn't unmarshal the JSON")
		return ""
	} else {
		for i := 0; i < len(locations); i++ {
			newLoc, err := ResolveLocation(server.Config, locations[i])
			if err != nil {
				ctx.Abort(422, err.Error())
				return "aaaaaaa"
			}
			resolvedLocations = append(resolvedLocations, newLoc)
		}
	}

	e := time.Since(t)

	fmt.Printf("resolved %d locations in %s\n", len(resolvedLocations), e)
	res, err := json.Marshal(resolvedLocations)
	if err != nil {
		ctx.Abort(500, err.Error())
		return "eeeee"
	}

	ctx.Header().Set("Access-Control-Allow-Origin", "*")
	ctx.ContentType("application/json")
	return string(res)
}
<|endoftext|>


<commit_before>package b2

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"
)

// B2 is an API client for Backblaze's B2. It contains all user account state
// and a private http client used to make requests.
type B2 struct {
	AccountID          string
	ApplicationKey     string
	AuthorizationToken string
	APIURL             string
	DownloadURL        string
	client             client
}

// The client interface is satisfied by an http.Client and a testClient.
type client interface {
	Do(*http.Request) (*http.Response, error)
}

// authResponse contains a successful B2 authentication response.
type authResponse struct {
	AccountID          string `json:"accountId"`
	AuthorizationToken string `json:"authorizationToken"`
	APIURL             string `json:"apiUrl"`
	DownloadURL        string `json:"downloadUrl"`
}

// APIError contains an error generated by the B2 API.
//
// All errors that the B2 API returns will be in this format, though not all
// errors returned by this libraries function calls will be APIErrors.
type APIError struct {
	Status  int64  `json:"status"`
	Code    string `json:"code"`
	Message string `json:"message"`
}

func (e APIError) Error() string {
	return fmt.Sprintf("Status: %d, Code: %s, Message: %s", e.Status, e.Code, e.Message)
}

// CreateB2 makes a new B2 client and authorizes it.
func CreateB2(accountID, appKey string) (*B2, error) {
	b2 := &B2{
		AccountID:      accountID,
		ApplicationKey: appKey,
		client:         http.DefaultClient,
	}
	return b2.createB2()
}

// createB2 executes the authorization of a B2 client.
func (b2 *B2) createB2() (*B2, error) {
	req, err := CreateRequest("GET", "https://api.backblaze.com/b2api/v1/b2_authorize_account", nil)
	if err != nil {
		return nil, err
	}
	req.SetBasicAuth(b2.AccountID, b2.ApplicationKey)
	resp, err := b2.client.Do(req)
	if err != nil {
		return nil, err
	}
	return b2.parseCreateB2(resp)
}

func (b2 *B2) parseCreateB2(resp *http.Response) (*B2, error) {
	ar := &authResponse{}
	err := parseResponse(resp, ar)
	if err != nil {
		return nil, err
	}
	b2.AuthorizationToken = ar.AuthorizationToken
	b2.APIURL = ar.APIURL
	b2.DownloadURL = ar.DownloadURL
	return b2, nil
}

// CreateRequest makes a http.Request that can be passed to http's Client.Do.
func CreateRequest(method, url string, request interface{}) (*http.Request, error) {
	body, err := json.Marshal(request)
	if err != nil {
		return nil, err
	}
	return http.NewRequest(method, url, bytes.NewReader(body))
}

// GetBzInfoHeaders returns a map of headers in a response that start with
// "X-Bz-Info-", which are file metadata that were uploaded with the file.
func GetBzInfoHeaders(resp *http.Response) map[string]string {
	out := map[string]string{}
	for k, v := range resp.Header {
		if strings.HasPrefix(k, "X-Bz-Info-") {
			// strip Bz prefix and grab first header
			out[k[10:]] = v[0]
		}
	}
	return out
}

// parseResponse routes a response to parse the body or an error.
func parseResponse(resp *http.Response, body interface{}) error {
	defer resp.Body.Close()
	if resp.StatusCode == 200 {
		return parseResponseBody(resp, body)
	} else {
		return parseAPIError(resp)
	}
}

// parseResponseBody parses a successful JSON response into the body param.
func parseResponseBody(resp *http.Response, body interface{}) error {
	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	return json.Unmarshal(b, body)
}

// parseAPIError parses and returns an APIError.
func parseAPIError(resp *http.Response) error {
	e := &APIError{}
	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	err = json.Unmarshal(b, e)
	if err != nil {
		return err
	}
	return e
}
<commit_msg>Remove unnecessary else statement<commit_after>package b2

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"
)

// B2 is an API client for Backblaze's B2. It contains all user account state
// and a private http client used to make requests.
type B2 struct {
	AccountID          string
	ApplicationKey     string
	AuthorizationToken string
	APIURL             string
	DownloadURL        string
	client             client
}

// The client interface is satisfied by an http.Client and a testClient.
type client interface {
	Do(*http.Request) (*http.Response, error)
}

// authResponse contains a successful B2 authentication response.
type authResponse struct {
	AccountID          string `json:"accountId"`
	AuthorizationToken string `json:"authorizationToken"`
	APIURL             string `json:"apiUrl"`
	DownloadURL        string `json:"downloadUrl"`
}

// APIError contains an error generated by the B2 API.
//
// All errors that the B2 API returns will be in this format, though not all
// errors returned by this libraries function calls will be APIErrors.
type APIError struct {
	Status  int64  `json:"status"`
	Code    string `json:"code"`
	Message string `json:"message"`
}

func (e APIError) Error() string {
	return fmt.Sprintf("Status: %d, Code: %s, Message: %s", e.Status, e.Code, e.Message)
}

// CreateB2 makes a new B2 client and authorizes it.
func CreateB2(accountID, appKey string) (*B2, error) {
	b2 := &B2{
		AccountID:      accountID,
		ApplicationKey: appKey,
		client:         http.DefaultClient,
	}
	return b2.createB2()
}

// createB2 executes the authorization of a B2 client.
func (b2 *B2) createB2() (*B2, error) {
	req, err := CreateRequest("GET", "https://api.backblaze.com/b2api/v1/b2_authorize_account", nil)
	if err != nil {
		return nil, err
	}
	req.SetBasicAuth(b2.AccountID, b2.ApplicationKey)
	resp, err := b2.client.Do(req)
	if err != nil {
		return nil, err
	}
	return b2.parseCreateB2(resp)
}

func (b2 *B2) parseCreateB2(resp *http.Response) (*B2, error) {
	ar := &authResponse{}
	err := parseResponse(resp, ar)
	if err != nil {
		return nil, err
	}
	b2.AuthorizationToken = ar.AuthorizationToken
	b2.APIURL = ar.APIURL
	b2.DownloadURL = ar.DownloadURL
	return b2, nil
}

// CreateRequest makes a http.Request that can be passed to http's Client.Do.
func CreateRequest(method, url string, request interface{}) (*http.Request, error) {
	body, err := json.Marshal(request)
	if err != nil {
		return nil, err
	}
	return http.NewRequest(method, url, bytes.NewReader(body))
}

// GetBzInfoHeaders returns a map of headers in a response that start with
// "X-Bz-Info-", which are file metadata that were uploaded with the file.
func GetBzInfoHeaders(resp *http.Response) map[string]string {
	out := map[string]string{}
	for k, v := range resp.Header {
		if strings.HasPrefix(k, "X-Bz-Info-") {
			// strip Bz prefix and grab first header
			out[k[10:]] = v[0]
		}
	}
	return out
}

// parseResponse routes a response to parse the body or an error.
func parseResponse(resp *http.Response, body interface{}) error {
	defer resp.Body.Close()
	if resp.StatusCode == 200 {
		return parseResponseBody(resp, body)
	}
	return parseAPIError(resp)
}

// parseResponseBody parses a successful JSON response into the body param.
func parseResponseBody(resp *http.Response, body interface{}) error {
	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	return json.Unmarshal(b, body)
}

// parseAPIError parses and returns an APIError.
func parseAPIError(resp *http.Response) error {
	e := &APIError{}
	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	err = json.Unmarshal(b, e)
	if err != nil {
		return err
	}
	return e
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"strings"
	"time"
)

const (
	host         = "api.pshdl.org"
	workspaceUrl = "/api/v0.1/workspace/"
)

// POST
// with name and email
type PshdlWorkspace struct {
	Id, Name, Email string
	Files           []PshdlApiFile
	LastValidation  int
	Validated       bool
}

func (wp PshdlWorkspace) String() string {
	if wp.Id == "" {
		return fmt.Sprintf("http://%s%s", host, workspaceUrl)
	}
	return fmt.Sprintf("http://%s%s%s", host, workspaceUrl, wp.Id)
}

type PshdlApiFile struct {
	Syntax string
	Type   string
	Record struct {
		RelPath      string
		FileURI      string
		LastModified int
	}
	Info struct {
		Created  int
		Problems []PshdlApiProblem
	}
}

func (file PshdlApiFile) String() (str string) {
	str = fmt.Sprintf("\nName: %s - ", file.Record.RelPath)
	if len(file.Info.Problems) > 0 {
		str += fmt.Sprintf("Problems:\n")
		for _, prob := range file.Info.Problems {
			str += fmt.Sprintf("%4d: %s\n", prob.Location.Line, prob.ErrorCode)
		}
	} else {
		str += fmt.Sprintf("No Problems.")
	}

	return
}

type PshdlApiProblem struct {
	Advise struct {
		Explanation string
		Message     string
		Solutions   []string
	}
	Location struct {
		Length, Line, OffsetInLine, TotalOffset int
	}
	Pid       int
	ErrorCode string
	Severity  string
	Syntax    bool
}

func OpenWorkspace(id string) (*PshdlWorkspace, error) {
	wp := &PshdlWorkspace{Id: id}
	if err := wp.parseWorkspace(); err != nil {
		return nil, err
	}

	return wp, nil
}

func NewWorkspace(name, email string) (*PshdlWorkspace, error) {
	wp := &PshdlWorkspace{Name: name, Email: email}
	if err := wp.createWorkspace(); err != nil {
		return nil, err
	}
	return wp, nil
}

func (wp *PshdlWorkspace) AddFile(path string) error {
	if wp.Id == "" {
		return fmt.Errorf("Workspace not Open.\n")
	}

	file, err := os.Open(path)
	if err != nil {
		return err
	}
	defer file.Close()

	body := &bytes.Buffer{}
	writer := multipart.NewWriter(body)

	fname := filepath.Base(path)
	part, err := writer.CreateFormFile("file", fname)
	if err != nil {
		return err
	}

	_, err = io.Copy(part, file)
	if err != nil {
		return err
	}

	err = writer.Close()
	if err != nil {
		return err
	}

	req, err := http.NewRequest("POST", wp.String(), body)
	req.Header.Set("Accept", "text/plain")
	req.Header.Set("Content-Type", writer.FormDataContentType())
	if err != nil {
		return err
	}

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != 201 {
		return fmt.Errorf("Could not save %s - %s\n", fname, resp.StatusCode)
	}

	return nil
}

func (wp *PshdlWorkspace) Validate() error {
	if wp.Id == "" {
		return fmt.Errorf("Workspace not Open.\n")
	}

	url := fmt.Sprintf("http://%s/api/v0.1/compiler/%s/validate", host, wp.Id)
	req, err := http.NewRequest("POST", url, nil)
	if err != nil {
		return err
	}
	req.Header.Set("Accept", "application/json")

	wp.parseApiRequest(req)
	if err != nil {
		return err
	}

	return nil
}

func (wp *PshdlWorkspace) DownloadAllFiles() error {

	// probe folder
	err := os.Mkdir(wp.Id, 0700)
	if err != nil {
		return err
	}

	done := make(chan bool)
	fileCount := len(wp.Files)
	start := time.Now()

	for _, file := range wp.Files {
		go func(file PshdlApiFile) {
			url := fmt.Sprintf("http://%s%s", host, file.Record.FileURI)
			resp, err := http.Get(url)
			if err != nil {
				fmt.Fprintf(os.Stderr, "Could not http.Get %s - %s\n", file.Record.RelPath, err)
				done <- false
				return
			}
			defer resp.Body.Close()

			fname := fmt.Sprintf("%s/%s", wp.Id, file.Record.RelPath)
			out, err := os.Create(fname)
			if err != nil {
				fmt.Fprintf(os.Stderr, "Could not os.Create file %s - %s\n", file.Record.RelPath, err)
				done <- false
				return
			}

			io.Copy(out, resp.Body)
			done <- true
		}(file)
	}

	for {
		select {
		case worked := <-done:
			if worked == false {
				close(done)
				return fmt.Errorf("Could not load all files.")
			}

			fileCount -= 1
			if fileCount == 0 {
				return nil
			}

		case <-time.After(3 * time.Second):
			fmt.Fprintf(os.Stderr, "Waiting.. %d files left. Duration: %s\n", fileCount, time.Since(start))
		}
	}
}

func (wp *PshdlWorkspace) createWorkspace() error {
	param := url.Values{}
	param.Set("name", wp.Name)
	param.Set("eMail", wp.Email)

	resp, err := http.PostForm(wp.String(), param)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != 201 {
		return fmt.Errorf("Workspace was not created - StatusCode:%d", resp.StatusCode)
	}

	body, err := ioutil.ReadAll(resp.Body)
	out := string(body)

	if !strings.HasPrefix(out, workspaceUrl) {
		return fmt.Errorf("No Workspace ID - %s", out)
	}
	wp.Id = out[len(workspaceUrl):]
	return nil
}

func (wp *PshdlWorkspace) parseWorkspace() error {
	req, err := http.NewRequest("GET", wp.String(), nil)
	req.Header.Set("Accept", "application/json")

	err = wp.parseApiRequest(req)
	if err != nil {
		return err
	}
	return nil
}

func (wp *PshdlWorkspace) parseApiRequest(req *http.Request) error {
	// fmt.Printf("parse Request:%+v\n", req)
	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode == 404 {
		return fmt.Errorf("Workspace or Resource not found")
	}

	if resp.StatusCode != 200 {
		return fmt.Errorf("Response Status %d", resp.StatusCode)
	}

	// fmt.Printf("parse Response:%+v\n", resp)
	decoder := json.NewDecoder(resp.Body)
	err = decoder.Decode(&wp)
	if err != nil {
		return err
	}
	return nil
}
<commit_msg>Fixed HTML download of workspace files.<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"strings"
	"time"
)

const (
	host         = "api.pshdl.org"
	workspaceUrl = "/api/v0.1/workspace/"
)

// POST
// with name and email
type PshdlWorkspace struct {
	Id, Name, Email string
	Files           []PshdlApiFile
	LastValidation  int
	Validated       bool
}

func (wp PshdlWorkspace) String() string {
	if wp.Id == "" {
		return fmt.Sprintf("http://%s%s", host, workspaceUrl)
	}
	return fmt.Sprintf("http://%s%s%s", host, workspaceUrl, wp.Id)
}

type PshdlApiFile struct {
	Syntax string
	Type   string
	Record struct {
		RelPath      string
		FileURI      string
		LastModified int
	}
	Info struct {
		Created  int
		Problems []PshdlApiProblem
	}
}

func (file PshdlApiFile) String() (str string) {
	str = fmt.Sprintf("\nName: %s - ", file.Record.RelPath)
	if len(file.Info.Problems) > 0 {
		str += fmt.Sprintf("Problems:\n")
		for _, prob := range file.Info.Problems {
			str += fmt.Sprintf("%4d: %s\n", prob.Location.Line, prob.ErrorCode)
		}
	} else {
		str += fmt.Sprintf("No Problems.")
	}

	return
}

type PshdlApiProblem struct {
	Advise struct {
		Explanation string
		Message     string
		Solutions   []string
	}
	Location struct {
		Length, Line, OffsetInLine, TotalOffset int
	}
	Pid       int
	ErrorCode string
	Severity  string
	Syntax    bool
}

func OpenWorkspace(id string) (*PshdlWorkspace, error) {
	wp := &PshdlWorkspace{Id: id}
	if err := wp.parseWorkspace(); err != nil {
		return nil, err
	}

	return wp, nil
}

func NewWorkspace(name, email string) (*PshdlWorkspace, error) {
	wp := &PshdlWorkspace{Name: name, Email: email}
	if err := wp.createWorkspace(); err != nil {
		return nil, err
	}
	return wp, nil
}

func (wp *PshdlWorkspace) AddFile(path string) error {
	if wp.Id == "" {
		return fmt.Errorf("Workspace not Open.\n")
	}

	file, err := os.Open(path)
	if err != nil {
		return err
	}
	defer file.Close()

	body := &bytes.Buffer{}
	writer := multipart.NewWriter(body)

	fname := filepath.Base(path)
	part, err := writer.CreateFormFile("file", fname)
	if err != nil {
		return err
	}

	_, err = io.Copy(part, file)
	if err != nil {
		return err
	}

	err = writer.Close()
	if err != nil {
		return err
	}

	req, err := http.NewRequest("POST", wp.String(), body)
	req.Header.Set("Accept", "text/plain")
	req.Header.Set("Content-Type", writer.FormDataContentType())
	if err != nil {
		return err
	}

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != 201 {
		return fmt.Errorf("Could not save %s - %s\n", fname, resp.StatusCode)
	}

	return nil
}

func (wp *PshdlWorkspace) Validate() error {
	if wp.Id == "" {
		return fmt.Errorf("Workspace not Open.\n")
	}

	url := fmt.Sprintf("http://%s/api/v0.1/compiler/%s/validate", host, wp.Id)
	req, err := http.NewRequest("POST", url, nil)
	if err != nil {
		return err
	}
	req.Header.Set("Accept", "application/json")

	wp.parseApiRequest(req)
	if err != nil {
		return err
	}

	return nil
}

func (wp *PshdlWorkspace) DownloadAllFiles() error {

	// probe folder
	err := os.Mkdir(wp.Id, 0700)
	if err != nil {
		return err
	}

	done := make(chan bool)
	fileCount := len(wp.Files)
	start := time.Now()

	for _, file := range wp.Files {
		go func(file PshdlApiFile) {
			url := fmt.Sprintf("http://%s%s", host, file.Record.FileURI)
			req, _ := http.NewRequest("GET", url, nil)
			req.Header.Set("Accept", "text/plain")
			client := &http.Client{}
			resp, err := client.Do(req)
			if err != nil {
				fmt.Fprintf(os.Stderr, "Could not http.Get %s - %s\n", file.Record.RelPath, err)
				done <- false
				return
			}
			defer resp.Body.Close()

			fname := fmt.Sprintf("%s/%s", wp.Id, file.Record.RelPath)
			out, err := os.Create(fname)
			if err != nil {
				fmt.Fprintf(os.Stderr, "Could not os.Create file %s - %s\n", file.Record.RelPath, err)
				done <- false
				return
			}

			io.Copy(out, resp.Body)
			done <- true
		}(file)
	}

	for {
		select {
		case worked := <-done:
			if worked == false {
				close(done)
				return fmt.Errorf("Could not load all files.")
			}

			fileCount -= 1
			if fileCount == 0 {
				return nil
			}

		case <-time.After(3 * time.Second):
			fmt.Fprintf(os.Stderr, "Waiting.. %d files left. Duration: %s\n", fileCount, time.Since(start))
		}
	}
}

func (wp *PshdlWorkspace) createWorkspace() error {
	param := url.Values{}
	param.Set("name", wp.Name)
	param.Set("eMail", wp.Email)

	resp, err := http.PostForm(wp.String(), param)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != 201 {
		return fmt.Errorf("Workspace was not created - StatusCode:%d", resp.StatusCode)
	}

	body, err := ioutil.ReadAll(resp.Body)
	out := string(body)

	if !strings.HasPrefix(out, workspaceUrl) {
		return fmt.Errorf("No Workspace ID - %s", out)
	}
	wp.Id = out[len(workspaceUrl):]
	return nil
}

func (wp *PshdlWorkspace) parseWorkspace() error {
	req, err := http.NewRequest("GET", wp.String(), nil)
	req.Header.Set("Accept", "application/json")

	err = wp.parseApiRequest(req)
	if err != nil {
		return err
	}
	return nil
}

func (wp *PshdlWorkspace) parseApiRequest(req *http.Request) error {
	// fmt.Printf("parse Request:%+v\n", req)
	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode == 404 {
		return fmt.Errorf("Workspace or Resource not found")
	}

	if resp.StatusCode != 200 {
		return fmt.Errorf("Response Status %d", resp.StatusCode)
	}

	// fmt.Printf("parse Response:%+v\n", resp)
	decoder := json.NewDecoder(resp.Body)
	err = decoder.Decode(&wp)
	if err != nil {
		return err
	}
	return nil
}
<|endoftext|>