<commit_before>/*
 * magic.go
 *
 * Copyright 2013 Krzysztof Wilczynski
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package magic

/*
#cgo LDFLAGS: -lmagic
#include "functions.h"
*/
import "C"

import (
	"fmt"
	"math"
	"os"
	"reflect"
	"runtime"
	"sort"
	"strings"
	"sync"
	"syscall"
	"unsafe"
)

type magic struct {
	sync.Mutex
	flags  int
	path   []string
	cookie C.magic_t
}

func (m *magic) close() {
	if m != nil && m.cookie != nil {
		C.magic_close(m.cookie)
		m.cookie = nil
	}
	runtime.SetFinalizer(m, nil)
}

type Magic struct {
	*magic
}

func New(files ...string) (*Magic, error) {
	mgc, err := open()
	if err != nil {
		return nil, err
	}

	if _, err := mgc.Load(files...); err != nil {
		return nil, err
	}
	return mgc, nil
}

func (mgc *Magic) Close() {
	mgc.Lock()
	defer mgc.Unlock()
	mgc.magic.close()
}

func (mgc *Magic) String() string {
	return fmt.Sprintf("Magic{flags:%d path:%s cookie:%p}",
		mgc.flags, mgc.path, mgc.cookie)
}

func (mgc *Magic) Path() ([]string, error) {
	mgc.Lock()
	defer mgc.Unlock()

	if mgc.cookie == nil {
		return []string{}, mgc.error()
	}

	if len(mgc.path) > 0 && os.Getenv("MAGIC") == "" {
		return mgc.path, nil
	}
	rv := C.GoString(C.magic_getpath_wrapper())
	mgc.path = strings.Split(rv, ":")
	return mgc.path, nil
}

func (mgc *Magic) Flags() (int, error) {
	mgc.Lock()
	defer mgc.Unlock()

	if mgc.cookie == nil {
		return -1, mgc.error()
	}
	return mgc.flags, nil
}

func (mgc *Magic) FlagsArray() ([]int, error) {
	mgc.Lock()
	defer mgc.Unlock()

	if mgc.cookie == nil {
		return []int{}, mgc.error()
	}

	flags := make([]int, 0)

	n := 0
	for i := mgc.flags; i > 0; i = i - n {
		n = int(math.Log2(float64(i)))
		n = int(math.Pow(2, float64(n)))
		flags = append(flags, n)
	}
	sort.Ints(flags)
	return flags, nil
}

func (mgc *Magic) SetFlags(flags int) error {
	mgc.Lock()
	defer mgc.Unlock()

	if mgc.cookie == nil {
		return mgc.error()
	}

	rv, err := C.magic_setflags_wrapper(mgc.cookie, C.int(flags))
	if rv < 0 && err != nil {
		errno := err.(syscall.Errno)
		return &MagicError{int(errno), errno.Error()}
	}

	mgc.flags = flags
	return nil
}

func (mgc *Magic) Load(files ...string) (bool, error) {
	mgc.Lock()
	defer mgc.Unlock()

	if mgc.cookie == nil {
		return false, mgc.error()
	}

	var cfiles *C.char
	defer C.free(unsafe.Pointer(cfiles))

	if len(files) > 0 {
		cfiles = C.CString(strings.Join(files, ":"))
	} else {
		cfiles = C.magic_getpath_wrapper()
	}

	if rv := C.magic_load_wrapper(mgc.cookie, cfiles, C.int(mgc.flags)); rv < 0 {
		return false, mgc.error()
	}
	mgc.path = strings.Split(C.GoString(cfiles), ":")
	return true, nil
}

func (mgc *Magic) Compile(files ...string) (bool, error) {
	mgc.Lock()
	defer mgc.Unlock()

	if mgc.cookie == nil {
		return false, mgc.error()
	}

	var cfiles *C.char
	if len(files) > 0 {
		cfiles = C.CString(strings.Join(files, ":"))
		defer C.free(unsafe.Pointer(cfiles))
	}

	if rv := C.magic_compile_wrapper(mgc.cookie, cfiles, C.int(mgc.flags)); rv < 0 {
		return false, mgc.error()
	}
	return true, nil
}

func (mgc *Magic) Check(files ...string) (bool, error) {
	mgc.Lock()
	defer mgc.Unlock()

	if mgc.cookie == nil {
		return false, mgc.error()
	}

	var cfiles *C.char
	if len(files) > 0 {
		cfiles = C.CString(strings.Join(files, ":"))
		defer C.free(unsafe.Pointer(cfiles))
	}

	if rv := C.magic_check_wrapper(mgc.cookie, cfiles, C.int(mgc.flags)); rv < 0 {
		return false, mgc.error()
	}
	return true, nil
}

func (mgc *Magic) File(filename string) (string, error) {
	mgc.Lock()
	defer mgc.Unlock()

	if mgc.cookie == nil {
		return "", mgc.error()
	}

	cfilename := C.CString(filename)
	defer C.free(unsafe.Pointer(cfilename))

	cstring := C.magic_file(mgc.cookie, cfilename)
	if cstring == nil {
		return "", mgc.error()
	}
	return C.GoString(cstring), nil
}

func (mgc *Magic) Buffer(buffer []byte) (string, error) {
	mgc.Lock()
	defer mgc.Unlock()

	if mgc.cookie == nil {
		return "", mgc.error()
	}

	p, length := unsafe.Pointer(&buffer[0]), C.size_t(len(buffer))

	cstring := C.magic_buffer(mgc.cookie, p, length)
	if cstring == nil {
		return "", mgc.error()
	}
	return C.GoString(cstring), nil
}

func (mgc *Magic) Descriptor(fd uintptr) (string, error) {
	mgc.Lock()
	defer mgc.Unlock()

	if mgc.cookie == nil {
		return "", mgc.error()
	}

	cstring := C.magic_descriptor(mgc.cookie, C.int(fd))
	if cstring == nil {
		return "", mgc.error()
	}
	return C.GoString(cstring), nil
}

func (mgc *Magic) Version() (int, error) {
	return Version()
}

func (mgc *Magic) VersionString() (string, error) {
	return VersionString()
}

func (mgc *Magic) error() *MagicError {
	if mgc.cookie == nil {
		errno := syscall.EINVAL
		return &MagicError{int(syscall.EINVAL), errno.Error()}
	}

	cstring := C.magic_error(mgc.cookie)
	if cstring != nil {
		errno := int(C.magic_errno(mgc.cookie))
		return &MagicError{errno, C.GoString(cstring)}
	}
	return &MagicError{-1, "unknown error"}
}

func (mgc *Magic) destroy() {
	mgc.Close()
}

func open() (*Magic, error) {
	rv := C.magic_open(C.int(NONE))
	if rv == nil {
		errno := syscall.ENOMEM
		return nil, &MagicError{int(errno), errno.Error()}
	}

	mgc := &Magic{&magic{flags: NONE, cookie: rv}}
	runtime.SetFinalizer(mgc.magic, (*magic).close)
	return mgc, nil
}

func Open(f func(magic *Magic) error, files ...string) (err error) {
	var ok bool
	errno := syscall.EINVAL

	if f == nil || reflect.TypeOf(f).Kind() != reflect.Func {
		return &MagicError{int(errno), errno.Error()}
	}

	mgc, err := New(files...)
	if err != nil {
		return err
	}
	defer mgc.Close()

	defer func() {
		if r := recover(); r != nil {
			err, ok = r.(error)
			if !ok {
				err = &MagicError{int(errno), fmt.Sprintf("%v", r)}
			}
		}
	}()

	return f(mgc)
}

func Compile(files ...string) (bool, error) {
	mgc, err := open()
	if err != nil {
		return false, err
	}
	defer mgc.Close()

	rv, err := mgc.Compile(files...)
	if err != nil {
		return rv, err
	}
	return rv, nil
}

func Check(files ...string) (bool, error) {
	mgc, err := open()
	if err != nil {
		return false, err
	}
	defer mgc.Close()

	rv, err := mgc.Check(files...)
	if err != nil {
		return rv, err
	}
	return rv, nil
}

func Version() (int, error) {
	rv, err := C.magic_version_wrapper()
	if rv < 0 && err != nil {
		errno := err.(syscall.Errno)
		return -1, &MagicError{int(errno), errno.Error()}
	}
	return int(rv), nil
}

func VersionString() (string, error) {
	rv, err := Version()
	if err != nil {
		return "", err
	}
	return fmt.Sprintf("%d.%02d", rv/100, rv%100), nil
}

func FileMime(filename string, files ...string) (string, error) {
	mgc, err := New(files...)
	if err != nil {
		return "", err
	}
	defer mgc.Close()

	mgc.SetFlags(MIME)
	return mgc.File(filename)
}

func FileEncoding(filename string, files ...string) (string, error) {
	mgc, err := New(files...)
	if err != nil {
		return "", err
	}
	defer mgc.Close()

	mgc.SetFlags(MIME_ENCODING)
	return mgc.File(filename)
}

func FileType(filename string, files ...string) (string, error) {
	mgc, err := New(files...)
	if err != nil {
		return "", err
	}
	defer mgc.Close()

	mgc.SetFlags(MIME_TYPE)
	return mgc.File(filename)
}

func BufferMime(buffer []byte, files ...string) (string, error) {
	mgc, err := New(files...)
	if err != nil {
		return "", err
	}
	defer mgc.Close()

	mgc.SetFlags(MIME)
	return mgc.Buffer(buffer)
}

func BufferEncoding(buffer []byte, files ...string) (string, error) {
	mgc, err := New(files...)
	if err != nil {
		return "", err
	}
	defer mgc.Close()

	mgc.SetFlags(MIME_ENCODING)
	return mgc.Buffer(buffer)
}

func BufferType(buffer []byte, files ...string) (string, error) {
	mgc, err := New(files...)
	if err != nil {
		return "", err
	}
	defer mgc.Close()

	mgc.SetFlags(MIME_TYPE)
	return mgc.Buffer(buffer)
}
<commit_msg>Remove surplus Version() and VersionString() that bind to the struct.<commit_after>/*
 * magic.go
 *
 * Copyright 2013 Krzysztof Wilczynski
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package magic

/*
#cgo LDFLAGS: -lmagic
#include "functions.h"
*/
import "C"

import (
	"fmt"
	"math"
	"os"
	"reflect"
	"runtime"
	"sort"
	"strings"
	"sync"
	"syscall"
	"unsafe"
)

type magic struct {
	sync.Mutex
	flags  int
	path   []string
	cookie C.magic_t
}

func (m *magic) close() {
	if m != nil && m.cookie != nil {
		C.magic_close(m.cookie)
		m.cookie = nil
	}
	runtime.SetFinalizer(m, nil)
}

type Magic struct {
	*magic
}

func New(files ...string) (*Magic, error) {
	mgc, err := open()
	if err != nil {
		return nil, err
	}

	if _, err := mgc.Load(files...); err != nil {
		return nil, err
	}
	return mgc, nil
}

func (mgc *Magic) Close() {
	mgc.Lock()
	defer mgc.Unlock()
	mgc.magic.close()
}

func (mgc *Magic) String() string {
	return fmt.Sprintf("Magic{flags:%d path:%s cookie:%p}",
		mgc.flags, mgc.path, mgc.cookie)
}

func (mgc *Magic) Path() ([]string, error) {
	mgc.Lock()
	defer mgc.Unlock()

	if mgc.cookie == nil {
		return []string{}, mgc.error()
	}

	if len(mgc.path) > 0 && os.Getenv("MAGIC") == "" {
		return mgc.path, nil
	}
	rv := C.GoString(C.magic_getpath_wrapper())
	mgc.path = strings.Split(rv, ":")
	return mgc.path, nil
}

func (mgc *Magic) Flags() (int, error) {
	mgc.Lock()
	defer mgc.Unlock()

	if mgc.cookie == nil {
		return -1, mgc.error()
	}
	return mgc.flags, nil
}

func (mgc *Magic) FlagsArray() ([]int, error) {
	mgc.Lock()
	defer mgc.Unlock()

	if mgc.cookie == nil {
		return []int{}, mgc.error()
	}

	flags := make([]int, 0)

	n := 0
	for i := mgc.flags; i > 0; i = i - n {
		n = int(math.Log2(float64(i)))
		n = int(math.Pow(2, float64(n)))
		flags = append(flags, n)
	}
	sort.Ints(flags)
	return flags, nil
}

func (mgc *Magic) SetFlags(flags int) error {
	mgc.Lock()
	defer mgc.Unlock()

	if mgc.cookie == nil {
		return mgc.error()
	}

	rv, err := C.magic_setflags_wrapper(mgc.cookie, C.int(flags))
	if rv < 0 && err != nil {
		errno := err.(syscall.Errno)
		return &MagicError{int(errno), errno.Error()}
	}

	mgc.flags = flags
	return nil
}

func (mgc *Magic) Load(files ...string) (bool, error) {
	mgc.Lock()
	defer mgc.Unlock()

	if mgc.cookie == nil {
		return false, mgc.error()
	}

	var cfiles *C.char
	defer C.free(unsafe.Pointer(cfiles))

	if len(files) > 0 {
		cfiles = C.CString(strings.Join(files, ":"))
	} else {
		cfiles = C.magic_getpath_wrapper()
	}

	if rv := C.magic_load_wrapper(mgc.cookie, cfiles, C.int(mgc.flags)); rv < 0 {
		return false, mgc.error()
	}
	mgc.path = strings.Split(C.GoString(cfiles), ":")
	return true, nil
}

func (mgc *Magic) Compile(files ...string) (bool, error) {
	mgc.Lock()
	defer mgc.Unlock()

	if mgc.cookie == nil {
		return false, mgc.error()
	}

	var cfiles *C.char
	if len(files) > 0 {
		cfiles = C.CString(strings.Join(files, ":"))
		defer C.free(unsafe.Pointer(cfiles))
	}

	if rv := C.magic_compile_wrapper(mgc.cookie, cfiles, C.int(mgc.flags)); rv < 0 {
		return false, mgc.error()
	}
	return true, nil
}

func (mgc *Magic) Check(files ...string) (bool, error) {
	mgc.Lock()
	defer mgc.Unlock()

	if mgc.cookie == nil {
		return false, mgc.error()
	}

	var cfiles *C.char
	if len(files) > 0 {
		cfiles = C.CString(strings.Join(files, ":"))
		defer C.free(unsafe.Pointer(cfiles))
	}

	if rv := C.magic_check_wrapper(mgc.cookie, cfiles, C.int(mgc.flags)); rv < 0 {
		return false, mgc.error()
	}
	return true, nil
}

func (mgc *Magic) File(filename string) (string, error) {
	mgc.Lock()
	defer mgc.Unlock()

	if mgc.cookie == nil {
		return "", mgc.error()
	}

	cfilename := C.CString(filename)
	defer C.free(unsafe.Pointer(cfilename))

	cstring := C.magic_file(mgc.cookie, cfilename)
	if cstring == nil {
		return "", mgc.error()
	}
	return C.GoString(cstring), nil
}

func (mgc *Magic) Buffer(buffer []byte) (string, error) {
	mgc.Lock()
	defer mgc.Unlock()

	if mgc.cookie == nil {
		return "", mgc.error()
	}

	p, length := unsafe.Pointer(&buffer[0]), C.size_t(len(buffer))

	cstring := C.magic_buffer(mgc.cookie, p, length)
	if cstring == nil {
		return "", mgc.error()
	}
	return C.GoString(cstring), nil
}

func (mgc *Magic) Descriptor(fd uintptr) (string, error) {
	mgc.Lock()
	defer mgc.Unlock()

	if mgc.cookie == nil {
		return "", mgc.error()
	}

	cstring := C.magic_descriptor(mgc.cookie, C.int(fd))
	if cstring == nil {
		return "", mgc.error()
	}
	return C.GoString(cstring), nil
}

func (mgc *Magic) error() *MagicError {
	if mgc.cookie == nil {
		errno := syscall.EINVAL
		return &MagicError{int(syscall.EINVAL), errno.Error()}
	}

	cstring := C.magic_error(mgc.cookie)
	if cstring != nil {
		errno := int(C.magic_errno(mgc.cookie))
		return &MagicError{errno, C.GoString(cstring)}
	}
	return &MagicError{-1, "unknown error"}
}

func (mgc *Magic) destroy() {
	mgc.Close()
}

func open() (*Magic, error) {
	rv := C.magic_open(C.int(NONE))
	if rv == nil {
		errno := syscall.ENOMEM
		return nil, &MagicError{int(errno), errno.Error()}
	}

	mgc := &Magic{&magic{flags: NONE, cookie: rv}}
	runtime.SetFinalizer(mgc.magic, (*magic).close)
	return mgc, nil
}

func Open(f func(magic *Magic) error, files ...string) (err error) {
	var ok bool
	errno := syscall.EINVAL

	if f == nil || reflect.TypeOf(f).Kind() != reflect.Func {
		return &MagicError{int(errno), errno.Error()}
	}

	mgc, err := New(files...)
	if err != nil {
		return err
	}
	defer mgc.Close()

	defer func() {
		if r := recover(); r != nil {
			err, ok = r.(error)
			if !ok {
				err = &MagicError{int(errno), fmt.Sprintf("%v", r)}
			}
		}
	}()

	return f(mgc)
}

func Compile(files ...string) (bool, error) {
	mgc, err := open()
	if err != nil {
		return false, err
	}
	defer mgc.Close()

	rv, err := mgc.Compile(files...)
	if err != nil {
		return rv, err
	}
	return rv, nil
}

func Check(files ...string) (bool, error) {
	mgc, err := open()
	if err != nil {
		return false, err
	}
	defer mgc.Close()

	rv, err := mgc.Check(files...)
	if err != nil {
		return rv, err
	}
	return rv, nil
}

func Version() (int, error) {
	rv, err := C.magic_version_wrapper()
	if rv < 0 && err != nil {
		errno := err.(syscall.Errno)
		return -1, &MagicError{int(errno), errno.Error()}
	}
	return int(rv), nil
}

func VersionString() (string, error) {
	rv, err := Version()
	if err != nil {
		return "", err
	}
	return fmt.Sprintf("%d.%02d", rv/100, rv%100), nil
}

func FileMime(filename string, files ...string) (string, error) {
	mgc, err := New(files...)
	if err != nil {
		return "", err
	}
	defer mgc.Close()

	mgc.SetFlags(MIME)
	return mgc.File(filename)
}

func FileEncoding(filename string, files ...string) (string, error) {
	mgc, err := New(files...)
	if err != nil {
		return "", err
	}
	defer mgc.Close()

	mgc.SetFlags(MIME_ENCODING)
	return mgc.File(filename)
}

func FileType(filename string, files ...string) (string, error) {
	mgc, err := New(files...)
	if err != nil {
		return "", err
	}
	defer mgc.Close()

	mgc.SetFlags(MIME_TYPE)
	return mgc.File(filename)
}

func BufferMime(buffer []byte, files ...string) (string, error) {
	mgc, err := New(files...)
	if err != nil {
		return "", err
	}
	defer mgc.Close()

	mgc.SetFlags(MIME)
	return mgc.Buffer(buffer)
}

func BufferEncoding(buffer []byte, files ...string) (string, error) {
	mgc, err := New(files...)
	if err != nil {
		return "", err
	}
	defer mgc.Close()

	mgc.SetFlags(MIME_ENCODING)
	return mgc.Buffer(buffer)
}

func BufferType(buffer []byte, files ...string) (string, error) {
	mgc, err := New(files...)
	if err != nil {
		return "", err
	}
	defer mgc.Close()

	mgc.SetFlags(MIME_TYPE)
	return mgc.Buffer(buffer)
}
<|endoftext|>


<commit_before>package mdtoc

import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"io"
	"os"
	"strings"
	"unicode"
)

const headerFormat = "- [%s](#%s)"
const atxHeader = "#"
const headerIdent = "    "

func isValidHeaderRune(r rune) bool {
	return unicode.IsNumber(r) || unicode.IsLetter(r) || unicode.IsSpace(r)
}

func normalizeHeader(header string) string {
	lowerNoHash := strings.TrimLeft(strings.ToLower(header), "#")
	noInvalidChars := []rune{}

	for _, r := range lowerNoHash {
		if isValidHeaderRune(r) {
			noInvalidChars = append(noInvalidChars, r)
		}
	}

	return strings.Replace(string(noInvalidChars), " ", "-", -1)
}

type writer func(data string)

func writeHeader(
	writeOutput writer,
	level int,
	header string,
	headersCount map[string]int,
) {
	normalizedHeader := normalizeHeader(header)
	count := headersCount[normalizedHeader]
	headersCount[normalizedHeader] = count + 1
	if count > 0 {
		normalizedHeader = fmt.Sprintf("%s-%d", normalizedHeader, count)
	}
	line := fmt.Sprintf(
		headerFormat,
		header,
		normalizedHeader,
	)
	for i := 1; i < level; i++ {
		writeOutput(headerIdent)
	}
	writeOutput(line + "\n")
}

func parseHeader(line string) (int, string, bool) {
	if !startsWithAtxHeader(line) {
		return 0, "", false
	}
	spaceTrimmed := strings.TrimRight(line, " ")
	parsed := strings.Split(spaceTrimmed, " ")
	if len(parsed) == 1 {
		return 0, "", false
	}
	headerlevel := len(parsed[0])
	header := parsed[1:]
	return headerlevel, strings.Join(header, " "), true
}

func startsWithAtxHeader(line string) bool {
	return strings.Index(line, atxHeader) == 0
}

func skipUntil(scanner *bufio.Scanner, stop func(string) bool) error {
	for scanner.Scan() {
		if stop(scanner.Text()) {
			return nil
		}
	}
	return errors.New("skipped all content")
}

func Generate(input io.Reader, output io.Writer) error {
	headerStart := "<!-- mdtocstart -->"
	tocHeader := "# Table of Contents"
	headerEnd := "<!-- mdtocend -->"
	scanner := bufio.NewScanner(input)
	headersCount := map[string]int{}

	var writeErr error
	writeOutput := func(s string) {
		if writeErr != nil {
			return
		}
		_, writeErr = output.Write([]byte(s))
	}

	var original bytes.Buffer
	var isCodeSection bool
	var wroteHeader bool

	for scanner.Scan() {
		line := scanner.Text()
		if strings.TrimSpace(line) == headerStart {
			err := skipUntil(scanner, func(l string) bool {
				return strings.TrimSpace(l) == headerEnd
			})
			if err != nil {
				return fmt.Errorf("error removing headers(corrupted headers?): %s", err)
			}
			err = skipUntil(scanner, func(l string) bool { return l != "" })
			if err != nil {
				// Just header present, removed headers
				return nil
			}
			line = scanner.Text()
		}
		_, err := original.Write([]byte(line + "\n"))
		if err != nil {
			return err
		}

		if strings.HasPrefix(line, "```") {
			isCodeSection = !isCodeSection
		}

		level, header, ok := parseHeader(line)
		if !ok || isCodeSection {
			continue
		}
		if !wroteHeader {
			writeOutput(headerStart)
			writeOutput("\n")
			writeOutput(tocHeader)
			writeOutput("\n\n")
			wroteHeader = true
		}
		writeHeader(writeOutput, level, header, headersCount)
	}

	if scanner.Err() != nil {
		return scanner.Err()
	}

	if wroteHeader {
		writeOutput(headerEnd)
		writeOutput("\n\n")
	}

	writeOutput(original.String())
	return writeErr
}

func GenerateFromFile(inputpath string, output io.Writer) error {
	file, err := os.Open(inputpath)
	defer file.Close()
	if err != nil {
		return fmt.Errorf("GenerateFromFile: error opening file: %s", err)
	}
	return Generate(file, output)
}

func GenerateInPlace(inputpath string) error {
	var output bytes.Buffer
	err := GenerateFromFile(inputpath, &output)
	if err != nil {
		return err
	}

	file, err := os.Create(inputpath)
	defer file.Close()
	if err != nil {
		// TODO: That is why we need a backup file for the original one :-)
		return fmt.Errorf("GenerateInPlace: unable to truncate file: %s", err)
	}

	expectedwrite := int64(output.Len())
	written, err := io.Copy(file, &output)
	if err != nil {
		// TODO: That is why we need a backup file for the original one :-)
		return fmt.Errorf("GenerateInPlace: unable to copy contents: %s", err)
	}
	if written != expectedwrite {
		// TODO: That is why we need a backup file for the original one :-)
		return fmt.Errorf(
			"GenerateInPlace: unable to copy contents: wrote %d expected %d",
			written,
			expectedwrite,
		)
	}
	return nil
}
<commit_msg>Fix wrong Close calls<commit_after>package mdtoc

import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"io"
	"os"
	"strings"
	"unicode"
)

const headerFormat = "- [%s](#%s)"
const atxHeader = "#"
const headerIdent = "    "

func isValidHeaderRune(r rune) bool {
	return unicode.IsNumber(r) || unicode.IsLetter(r) || unicode.IsSpace(r)
}

func normalizeHeader(header string) string {
	lowerNoHash := strings.TrimLeft(strings.ToLower(header), "#")
	noInvalidChars := []rune{}

	for _, r := range lowerNoHash {
		if isValidHeaderRune(r) {
			noInvalidChars = append(noInvalidChars, r)
		}
	}

	return strings.Replace(string(noInvalidChars), " ", "-", -1)
}

type writer func(data string)

func writeHeader(
	writeOutput writer,
	level int,
	header string,
	headersCount map[string]int,
) {
	normalizedHeader := normalizeHeader(header)
	count := headersCount[normalizedHeader]
	headersCount[normalizedHeader] = count + 1
	if count > 0 {
		normalizedHeader = fmt.Sprintf("%s-%d", normalizedHeader, count)
	}
	line := fmt.Sprintf(
		headerFormat,
		header,
		normalizedHeader,
	)
	for i := 1; i < level; i++ {
		writeOutput(headerIdent)
	}
	writeOutput(line + "\n")
}

func parseHeader(line string) (int, string, bool) {
	if !startsWithAtxHeader(line) {
		return 0, "", false
	}
	spaceTrimmed := strings.TrimRight(line, " ")
	parsed := strings.Split(spaceTrimmed, " ")
	if len(parsed) == 1 {
		return 0, "", false
	}
	headerlevel := len(parsed[0])
	header := parsed[1:]
	return headerlevel, strings.Join(header, " "), true
}

func startsWithAtxHeader(line string) bool {
	return strings.Index(line, atxHeader) == 0
}

func skipUntil(scanner *bufio.Scanner, stop func(string) bool) error {
	for scanner.Scan() {
		if stop(scanner.Text()) {
			return nil
		}
	}
	return errors.New("skipped all content")
}

func Generate(input io.Reader, output io.Writer) error {
	headerStart := "<!-- mdtocstart -->"
	tocHeader := "# Table of Contents"
	headerEnd := "<!-- mdtocend -->"
	scanner := bufio.NewScanner(input)
	headersCount := map[string]int{}

	var writeErr error
	writeOutput := func(s string) {
		if writeErr != nil {
			return
		}
		_, writeErr = output.Write([]byte(s))
	}

	var original bytes.Buffer
	var isCodeSection bool
	var wroteHeader bool

	for scanner.Scan() {
		line := scanner.Text()
		if strings.TrimSpace(line) == headerStart {
			err := skipUntil(scanner, func(l string) bool {
				return strings.TrimSpace(l) == headerEnd
			})
			if err != nil {
				return fmt.Errorf("error removing headers(corrupted headers?): %s", err)
			}
			err = skipUntil(scanner, func(l string) bool { return l != "" })
			if err != nil {
				// Just header present, removed headers
				return nil
			}
			line = scanner.Text()
		}
		_, err := original.Write([]byte(line + "\n"))
		if err != nil {
			return err
		}

		if strings.HasPrefix(line, "```") {
			isCodeSection = !isCodeSection
		}

		level, header, ok := parseHeader(line)
		if !ok || isCodeSection {
			continue
		}
		if !wroteHeader {
			writeOutput(headerStart)
			writeOutput("\n")
			writeOutput(tocHeader)
			writeOutput("\n\n")
			wroteHeader = true
		}
		writeHeader(writeOutput, level, header, headersCount)
	}

	if scanner.Err() != nil {
		return scanner.Err()
	}

	if wroteHeader {
		writeOutput(headerEnd)
		writeOutput("\n\n")
	}

	writeOutput(original.String())
	return writeErr
}

func GenerateFromFile(inputpath string, output io.Writer) error {
	file, err := os.Open(inputpath)
	if err != nil {
		return fmt.Errorf("GenerateFromFile: error opening file: %s", err)
	}
	defer file.Close()
	return Generate(file, output)
}

func GenerateInPlace(inputpath string) error {
	var output bytes.Buffer
	err := GenerateFromFile(inputpath, &output)
	if err != nil {
		return err
	}

	file, err := os.Create(inputpath)
	if err != nil {
		// TODO: That is why we need a backup file for the original one :-)
		return fmt.Errorf("GenerateInPlace: unable to truncate file: %s", err)
	}
	defer file.Close()

	expectedwrite := int64(output.Len())
	written, err := io.Copy(file, &output)
	if err != nil {
		// TODO: That is why we need a backup file for the original one :-)
		return fmt.Errorf("GenerateInPlace: unable to copy contents: %s", err)
	}
	if written != expectedwrite {
		// TODO: That is why we need a backup file for the original one :-)
		return fmt.Errorf(
			"GenerateInPlace: unable to copy contents: wrote %d expected %d",
			written,
			expectedwrite,
		)
	}
	return nil
}
<|endoftext|>


<commit_before>// Copyright (c) 2016-2017 Eric Barkie. All rights reserved.
// Use of this source code is governed by the MIT license
// that can be found in the LICENSE file.

package main

import (
	"fmt"
	"math"
	"time"

	"github.com/ebarkie/weatherlink/packet"
	"github.com/ebarkie/weatherlink/units"
)

// metar generates a report string for a given Loop struct.
func metar(l loop) string {
	// Type
	s := "METAR"

	// Date/Time
	s += fmt.Sprintf(" %sZ", l.Timestamp.In(time.UTC).Format("021504"))

	// Report Modifier
	s += " AUTO" // Indicates a fully automated report with no human intervention

	// Wind
	s += fmt.Sprintf(" %03d%02.f", l.Wind.Cur.Dir, units.Kn(float64(l.Wind.Cur.Speed)))
	if units.Kn(l.Wind.Gust.Last10MinSpeed) >= 0.50 {
		s += fmt.Sprintf("G%02.f", units.Kn(l.Wind.Gust.Last10MinSpeed))
	}
	s += "KT"

	// Weather Phenomena
	if l.Rain.Rate >= 1.0 { // Heavy
		s += " +RA"
	} else if l.Rain.Rate >= 0.5 { // Moderate
		s += " RA"
	} else if l.Rain.Rate > 0.0 { // Light
		s += " -RA"
	}

	// Temperature/Dew Point
	if t := units.C(l.OutTemp); t < 0.0 {
		s += fmt.Sprintf(" M%02.f", t*-1)
	} else {
		s += fmt.Sprintf(" %02.f", t)
	}
	if t := units.C(l.DewPoint); t < 0.0 {
		s += fmt.Sprintf("/M%02.f", t*-1)
	} else {
		s += fmt.Sprintf("/%02.f", t)
	}

	// Altimeter (in inches)
	s += fmt.Sprintf(" A%04.f", l.Bar.Altimeter*100)

	// Remarks
	s += " RMK AO1" // Automated station without a precipitation descriminator

	// Pressure Rising or Falling Rapidly
	if l.Bar.Trend == packet.RisingRapid {
		s += " PRESRR"
	} else if l.Bar.Trend == packet.FallingRapid {
		s += " PRESFR"
	}

	// Sea Level Pressure
	_, d := math.Modf(units.Mb(l.Bar.SeaLevel) / 100)
	s += fmt.Sprintf(" SLP%03.f", d*1000)

	// Hourly Precipitation Amount
	if l.Rain.Accum.LastHour > 0.0 {
		s += fmt.Sprintf(" P%04.f", l.Rain.Accum.LastHour*100)
	}

	// 24-Hour Precipitation Amount
	if l.Rain.Accum.Last24Hours > 0.0 {
		s += fmt.Sprintf(" 7%04.f", l.Rain.Accum.Last24Hours*100)
	}

	// Hourly Temperature and Dew Point
	if t := units.C(l.OutTemp); t < 0.0 {
		s += fmt.Sprintf(" T1%03.f", t*-10)
	} else {
		s += fmt.Sprintf(" T0%03.f", t*10)
	}
	if t := units.C(l.DewPoint); t < 0.0 {
		s += fmt.Sprintf("1%03.f", t*-10)
	} else {
		s += fmt.Sprintf("0%03.f", t*10)
	}

	return s
}
<commit_msg>Update units package<commit_after>// Copyright (c) 2016-2017 Eric Barkie. All rights reserved.
// Use of this source code is governed by the MIT license
// that can be found in the LICENSE file.

package main

import (
	"fmt"
	"math"
	"time"

	"github.com/ebarkie/weatherlink/packet"
	"github.com/ebarkie/weatherlink/units"
)

// metar generates a report string for a given Loop struct.
func metar(l loop) string {
	// Type
	s := "METAR"

	// Date/Time
	s += fmt.Sprintf(" %sZ", l.Timestamp.In(time.UTC).Format("021504"))

	// Report Modifier
	s += " AUTO" // Indicates a fully automated report with no human intervention

	// Wind
	s += fmt.Sprintf(" %03d%02.f", l.Wind.Cur.Dir, units.FromMPH(float64(l.Wind.Cur.Speed)).Kn())
	if units.FromMPH(l.Wind.Gust.Last10MinSpeed).Kn() >= 0.50 {
		s += fmt.Sprintf("G%02.f", units.FromMPH(l.Wind.Gust.Last10MinSpeed).Kn())
	}
	s += "KT"

	// Weather Phenomena
	if l.Rain.Rate >= 1.0 { // Heavy
		s += " +RA"
	} else if l.Rain.Rate >= 0.5 { // Moderate
		s += " RA"
	} else if l.Rain.Rate > 0.0 { // Light
		s += " -RA"
	}

	// Temperature/Dew Point
	if t := units.FromF(l.OutTemp).C(); t < 0.0 {
		s += fmt.Sprintf(" M%02.f", t*-1)
	} else {
		s += fmt.Sprintf(" %02.f", t)
	}
	if t := units.FromF(l.DewPoint).C(); t < 0.0 {
		s += fmt.Sprintf("/M%02.f", t*-1)
	} else {
		s += fmt.Sprintf("/%02.f", t)
	}

	// Altimeter (in inches)
	s += fmt.Sprintf(" A%04.f", l.Bar.Altimeter*100)

	// Remarks
	s += " RMK AO1" // Automated station without a precipitation descriminator

	// Pressure Rising or Falling Rapidly
	if l.Bar.Trend == packet.RisingRapid {
		s += " PRESRR"
	} else if l.Bar.Trend == packet.FallingRapid {
		s += " PRESFR"
	}

	// Sea Level Pressure
	_, d := math.Modf(units.FromMercuryIn(l.Bar.SeaLevel).Mb() / 100)
	s += fmt.Sprintf(" SLP%03.f", d*1000)

	// Hourly Precipitation Amount
	if l.Rain.Accum.LastHour > 0.0 {
		s += fmt.Sprintf(" P%04.f", l.Rain.Accum.LastHour*100)
	}

	// 24-Hour Precipitation Amount
	if l.Rain.Accum.Last24Hours > 0.0 {
		s += fmt.Sprintf(" 7%04.f", l.Rain.Accum.Last24Hours*100)
	}

	// Hourly Temperature and Dew Point
	if t := units.FromF(l.OutTemp).C(); t < 0.0 {
		s += fmt.Sprintf(" T1%03.f", t*-10)
	} else {
		s += fmt.Sprintf(" T0%03.f", t*10)
	}
	if t := units.FromF(l.DewPoint).C(); t < 0.0 {
		s += fmt.Sprintf("1%03.f", t*-10)
	} else {
		s += fmt.Sprintf("0%03.f", t*10)
	}

	return s
}
<|endoftext|>


<commit_before>package jsonschema

import (
	"encoding/json"
	"reflect"
	"strings"
)

type JSONSchema struct {
	Type       string                 `json:"type"`
	Items      *JSONSchemaItems       `json:"items,omitempty"`
	Properties map[string]*JSONSchema `json:"properties,omitempty"`
	Required   []string               `json:"required,omitempty"`
}

type JSONSchemaItems struct {
	Type string `json:"type,omitempty"`
}

func (j *JSONSchema) Marshal() ([]byte, error) {
	return json.MarshalIndent(j, "", "  ")
}

func (j *JSONSchema) String() string {
	json, _ := j.Marshal()
	return string(json)
}

func (j *JSONSchema) Load(variable interface{}) {
	value := reflect.ValueOf(variable).Elem()
	j.doLoad(value, tagOptions(""))
}

func (j *JSONSchema) doLoad(value reflect.Value, opts tagOptions) {
	kind := value.Kind()

	j.Type = getTypeFromMapping(kind)
	switch kind {
	case reflect.Slice:
		j.doLoadFromSlice(value)
	case reflect.Map:
		j.doLoadFromMap(value)
	case reflect.Struct:
		j.doLoadFromStruct(value)
	}
}

func (j *JSONSchema) doLoadFromSlice(valueObject reflect.Value) {
	k := valueObject.Type().Elem().Kind()
	if k == reflect.Uint8 {
		j.Type = "string"
	} else {
		j.Items = &JSONSchemaItems{Type: getTypeFromMapping(k)}
	}
}

func (j *JSONSchema) doLoadFromMap(valueObject reflect.Value) {
	k := valueObject.Type().Elem().Kind()
	j.Properties = make(map[string]*JSONSchema, 0)
	j.Properties[".*"] = &JSONSchema{Type: getTypeFromMapping(k)}
}

func (j *JSONSchema) doLoadFromStruct(valueObject reflect.Value) {
	j.Type = "object"
	j.Properties = make(map[string]*JSONSchema, 0)
	typeObject := valueObject.Type()

	count := valueObject.NumField()
	for i := 0; i < count; i++ {
		field := typeObject.Field(i)
		value := valueObject.Field(i)

		tag := field.Tag.Get("json")
		name, opts := parseTag(tag)
		if name == "" {
			name = field.Name
		}

		j.Properties[name] = &JSONSchema{}
		j.Properties[name].doLoad(value, opts)

		if !opts.Contains("omitempty") {
			j.Required = append(j.Required, name)
		}
	}
}

var mapping = map[reflect.Kind]string{
	reflect.Bool:    "bool",
	reflect.Int:     "integer",
	reflect.Int8:    "integer",
	reflect.Int16:   "integer",
	reflect.Int32:   "integer",
	reflect.Int64:   "integer",
	reflect.Uint:    "integer",
	reflect.Uint8:   "integer",
	reflect.Uint16:  "integer",
	reflect.Uint32:  "integer",
	reflect.Uint64:  "integer",
	reflect.Float32: "number",
	reflect.Float64: "number",
	reflect.String:  "string",
	reflect.Slice:   "array",
	reflect.Struct:  "object",
	reflect.Map:     "object",
}

func getTypeFromMapping(k reflect.Kind) string {
	if t, ok := mapping[k]; ok {
		return t
	}

	return ""
}

type tagOptions string

func parseTag(tag string) (string, tagOptions) {
	if idx := strings.Index(tag, ","); idx != -1 {
		return tag[:idx], tagOptions(tag[idx+1:])
	}
	return tag, tagOptions("")
}

func (o tagOptions) Contains(optionName string) bool {
	if len(o) == 0 {
		return false
	}
	s := string(o)
	for s != "" {
		var next string
		i := strings.Index(s, ",")
		if i >= 0 {
			s, next = s[:i], s[i+1:]
		}
		if s == optionName {
			return true
		}
		s = next
	}
	return false
}
<commit_msg>working with types instead of values<commit_after>package jsonschema

import (
	"encoding/json"
	"reflect"
	"strings"
)

type JSONSchema struct {
	Type       string                 `json:"type"`
	Items      *JSONSchemaItems       `json:"items,omitempty"`
	Properties map[string]*JSONSchema `json:"properties,omitempty"`
	Required   []string               `json:"required,omitempty"`
}

type JSONSchemaItems struct {
	Type string `json:"type,omitempty"`
}

func (j *JSONSchema) Marshal() ([]byte, error) {
	return json.MarshalIndent(j, "", "    ")
}

func (j *JSONSchema) String() string {
	json, _ := j.Marshal()
	return string(json)
}

func (j *JSONSchema) Load(variable interface{}) {
	value := reflect.ValueOf(variable)
	j.doLoad(value.Type(), tagOptions(""))
}

func (j *JSONSchema) doLoad(t reflect.Type, opts tagOptions) {
	kind := t.Kind()

	j.Type = getTypeFromMapping(kind)
	switch kind {
	case reflect.Slice:
		j.doLoadFromSlice(t)
	case reflect.Map:
		j.doLoadFromMap(t)
	case reflect.Struct:
		j.doLoadFromStruct(t)
	case reflect.Ptr:
		j.doLoad(t.Elem(), opts)
	}
}

func (j *JSONSchema) doLoadFromSlice(t reflect.Type) {
	k := t.Elem().Kind()
	if k == reflect.Uint8 {
		j.Type = "string"
	} else {
		j.Items = &JSONSchemaItems{Type: getTypeFromMapping(k)}
	}
}

func (j *JSONSchema) doLoadFromMap(t reflect.Type) {
	k := t.Elem().Kind()
	j.Properties = make(map[string]*JSONSchema, 0)
	j.Properties[".*"] = &JSONSchema{Type: getTypeFromMapping(k)}
}

func (j *JSONSchema) doLoadFromStruct(t reflect.Type) {
	j.Type = "object"
	j.Properties = make(map[string]*JSONSchema, 0)

	count := t.NumField()
	for i := 0; i < count; i++ {
		field := t.Field(i)

		tag := field.Tag.Get("json")
		name, opts := parseTag(tag)
		if name == "" {
			name = field.Name
		}

		j.Properties[name] = &JSONSchema{}
		j.Properties[name].doLoad(field.Type, opts)

		if !opts.Contains("omitempty") {
			j.Required = append(j.Required, name)
		}
	}
}

var mapping = map[reflect.Kind]string{
	reflect.Bool:    "bool",
	reflect.Int:     "integer",
	reflect.Int8:    "integer",
	reflect.Int16:   "integer",
	reflect.Int32:   "integer",
	reflect.Int64:   "integer",
	reflect.Uint:    "integer",
	reflect.Uint8:   "integer",
	reflect.Uint16:  "integer",
	reflect.Uint32:  "integer",
	reflect.Uint64:  "integer",
	reflect.Float32: "number",
	reflect.Float64: "number",
	reflect.String:  "string",
	reflect.Slice:   "array",
	reflect.Struct:  "object",
	reflect.Map:     "object",
}

func getTypeFromMapping(k reflect.Kind) string {
	if t, ok := mapping[k]; ok {
		return t
	}

	return ""
}

type tagOptions string

func parseTag(tag string) (string, tagOptions) {
	if idx := strings.Index(tag, ","); idx != -1 {
		return tag[:idx], tagOptions(tag[idx+1:])
	}
	return tag, tagOptions("")
}

func (o tagOptions) Contains(optionName string) bool {
	if len(o) == 0 {
		return false
	}
	s := string(o)
	for s != "" {
		var next string
		i := strings.Index(s, ",")
		if i >= 0 {
			s, next = s[:i], s[i+1:]
		}
		if s == optionName {
			return true
		}
		s = next
	}
	return false
}
<|endoftext|>


<commit_before>// Package speaker implements playback of beep.Streamer values through physical speakers.
package speaker

import (
	"sync"

	"github.com/faiface/beep"
	"github.com/hajimehoshi/oto"
	"github.com/pkg/errors"
)

var (
	mu      sync.Mutex
	mixer   beep.Mixer
	samples [][2]float64
	buf     []byte
	context *oto.Context
	player  *oto.Player
	done    chan struct{}
)

// Init initializes audio playback through speaker. Must be called before using this package.
//
// The bufferSize argument specifies the number of samples of the speaker's buffer. Bigger
// bufferSize means lower CPU usage and more reliable playback. Lower bufferSize means better
// responsiveness and less delay.
func Init(sampleRate beep.SampleRate, bufferSize int) error {
	mu.Lock()
	defer mu.Unlock()

	Close()

	mixer = beep.Mixer{}

	numBytes := bufferSize * 4
	samples = make([][2]float64, bufferSize)
	buf = make([]byte, numBytes)

	var err error
	context, err = oto.NewContext(int(sampleRate), 2, 2, numBytes)
	if err != nil {
		return errors.Wrap(err, "failed to initialize speaker")
	}
	player = context.NewPlayer()

	done = make(chan struct{})

	go func() {
		for {
			select {
			default:
				update()
			case <-done:
				return
			}
		}
	}()

	return nil
}

// Close closes playback and driver. In most cases, there is certainly no need to call Close
// even when program doesn't play anymore and it won't exist, because in properly set systems,
// the default mixer handles multiple processes. It's only when the default device is not virtual
// but hardware device that you'll probably want to manually manage the device from your application.
func Close() {
	if player != nil {
		done <- struct{}{}
		player.Close()
		context.Close()
		player = nil
	}
}

// Lock locks the speaker. While locked, speaker won't pull new data from the playing Stramers. Lock
// if you want to modify any currently playing Streamers to avoid race conditions.
//
// Always lock speaker for as little time as possible, to avoid playback glitches.
func Lock() {
	mu.Lock()
}

// Unlock unlocks the speaker. Call after modifying any currently playing Streamer.
func Unlock() {
	mu.Unlock()
}

// Play starts playing all provided Streamers through the speaker.
func Play(s ...beep.Streamer) {
	mu.Lock()
	mixer.Add(s...)
	mu.Unlock()
}

// Clear removes all currently playing Streamers from the speaker.
func Clear() {
	mu.Lock()
	mixer.Clear()
	mu.Unlock()
}

// update pulls new data from the playing Streamers and sends it to the speaker. Blocks until the
// data is sent and started playing.
func update() {
	mu.Lock()
	mixer.Stream(samples)
	mu.Unlock()

	for i := range samples {
		for c := range samples[i] {
			val := samples[i][c]
			if val < -1 {
				val = -1
			}
			if val > +1 {
				val = +1
			}
			valInt16 := int16(val * (1<<15 - 1))
			low := byte(valInt16)
			high := byte(valInt16 >> 8)
			buf[i*4+c*2+0] = low
			buf[i*4+c*2+1] = high
		}
	}

	player.Write(buf)
}
<commit_msg>added Close method to speaker<commit_after>// Package speaker implements playback of beep.Streamer values through physical speakers.
package speaker

import (
	"sync"

	"github.com/faiface/beep"
	"github.com/hajimehoshi/oto"
	"github.com/pkg/errors"
)

var (
	mu      sync.Mutex
	mixer   beep.Mixer
	samples [][2]float64
	buf     []byte
	context *oto.Context
	player  *oto.Player
	done    chan struct{}
)

// Init initializes audio playback through speaker. Must be called before using this package.
//
// The bufferSize argument specifies the number of samples of the speaker's buffer. Bigger
// bufferSize means lower CPU usage and more reliable playback. Lower bufferSize means better
// responsiveness and less delay.
func Init(sampleRate beep.SampleRate, bufferSize int) error {
	mu.Lock()
	defer mu.Unlock()

	Close()

	mixer = beep.Mixer{}

	numBytes := bufferSize * 4
	samples = make([][2]float64, bufferSize)
	buf = make([]byte, numBytes)

	var err error
	context, err = oto.NewContext(int(sampleRate), 2, 2, numBytes)
	if err != nil {
		return errors.Wrap(err, "failed to initialize speaker")
	}
	player = context.NewPlayer()

	done = make(chan struct{})

	go func() {
		for {
			select {
			default:
				update()
			case <-done:
				return
			}
		}
	}()

	return nil
}

// Close closes playback and driver. In most cases, there is certainly no need to call Close
// even when program doesn't play anymore and it won't exist, because in properly set systems,
// the default mixer handles multiple processes. It's only when the default device is not virtual
// but hardware device that you'll probably want to manually manage the device from your application.
func Close() {
	if player != nil {
		if done != nil {
			done <- struct{}{}
			done = nil
		}
		player.Close()
		context.Close()
		player = nil
	}
}

// Lock locks the speaker. While locked, speaker won't pull new data from the playing Stramers. Lock
// if you want to modify any currently playing Streamers to avoid race conditions.
//
// Always lock speaker for as little time as possible, to avoid playback glitches.
func Lock() {
	mu.Lock()
}

// Unlock unlocks the speaker. Call after modifying any currently playing Streamer.
func Unlock() {
	mu.Unlock()
}

// Play starts playing all provided Streamers through the speaker.
func Play(s ...beep.Streamer) {
	mu.Lock()
	mixer.Add(s...)
	mu.Unlock()
}

// Clear removes all currently playing Streamers from the speaker.
func Clear() {
	mu.Lock()
	mixer.Clear()
	mu.Unlock()
}

// update pulls new data from the playing Streamers and sends it to the speaker. Blocks until the
// data is sent and started playing.
func update() {
	mu.Lock()
	mixer.Stream(samples)
	mu.Unlock()

	for i := range samples {
		for c := range samples[i] {
			val := samples[i][c]
			if val < -1 {
				val = -1
			}
			if val > +1 {
				val = +1
			}
			valInt16 := int16(val * (1<<15 - 1))
			low := byte(valInt16)
			high := byte(valInt16 >> 8)
			buf[i*4+c*2+0] = low
			buf[i*4+c*2+1] = high
		}
	}

	player.Write(buf)
}
<|endoftext|>


<commit_before>package checker

import (
	"bytes"
	"net/url"
	"regexp"
	"strings"

	"github.com/asciimoo/privacyscore/penalty"
	"github.com/asciimoo/privacyscore/utils"
)

// TODO
var URLREGEXP *regexp.Regexp = regexp.MustCompile("url\\(['\"]([\u0009\u0021\u0023-\u0026\u0028\u002a-\u007E]+)['\"]\\)")

type CSSChecker struct{}

func (_ *CSSChecker) Check(c *CheckJob, p *PageInfo) {
	if !strings.Contains(strings.ToLower(p.ContentType), "css") {
		return
	}
	for _, b := range URLREGEXP.FindAll(p.ResponseBody, 128) {
		if len(b) < 6 {
			break
		}
		switch b[4] {
		case byte('"'), byte('\''):
			b = b[5 : len(b)-2]
		default:
			b = b[4 : len(b)-1]
		}
		if bytes.HasPrefix(b, []byte("data:")) {
			break
		}
		u, err := url.Parse(string(b))
		if err != nil {
			break
		}
		if utils.IsForeignHost(u.Host, p.Domain) {
			c.Result.Penalties.Add(penalty.P_EXTERNAL_RESOURCE, utils.CropSubdomains(u.Host))
		} else {
			c.CheckURL(utils.GetFullURL(u, p.URL))
		}
	}
}
<commit_msg>[enh] use handleSiteURL in css checker too<commit_after>package checker

import (
	"bytes"
	"regexp"
	"strings"

	"github.com/asciimoo/privacyscore/penalty"
)

// TODO
var URLREGEXP *regexp.Regexp = regexp.MustCompile("url\\(['\"]([\u0009\u0021\u0023-\u0026\u0028\u002a-\u007E]+)['\"]\\)")

type CSSChecker struct{}

func (_ *CSSChecker) Check(c *CheckJob, p *PageInfo) {
	if !strings.Contains(strings.ToLower(p.ContentType), "css") {
		return
	}
	for _, b := range URLREGEXP.FindAll(p.ResponseBody, 128) {
		if len(b) < 6 {
			break
		}
		switch b[4] {
		case byte('"'), byte('\''):
			b = b[5 : len(b)-2]
		default:
			b = b[4 : len(b)-1]
		}
		if !bytes.HasPrefix(b, []byte("data:")) {
			handleSiteURL(string(b), c, p, penalty.P_EXTERNAL_RESOURCE)
		}
	}
}
<|endoftext|>


<commit_before>package timber

import (
	"bytes"
	"compress/gzip"
	"fmt"
	"io"
	"os"
	"strings"
	"sync"
	"text/template"
	"time"
)

type FilenameFields struct {
	Hostname string
	Date     time.Time
	Pid      int
}

func GetFilenameFields() *FilenameFields {
	h, _ := os.Hostname()
	return &FilenameFields{
		Hostname: h,
		Date:     time.Now(),
		Pid:      os.Getpid(),
	}
}

func preprocessFilename(name string) string {
	t := template.Must(template.New("filename").Parse(name))
	buf := new(bytes.Buffer)
	t.Execute(buf, GetFilenameFields())
	return buf.String()
}

type FileWriter struct {
	wr              *BufferedWriter
	BaseFilename    string
	currentFilename string
	mutex           *sync.RWMutex
}

// This writer has a buffer that I don't ever bother to flush, so it may take a while
// to see messages.  Filenames ending in .gz will automatically be compressed on write.
// Filename string is proccessed through the template library using the FilenameFields
// struct.
func NewFileWriter(name string) (*FileWriter, error) {
	w := &FileWriter{
		BaseFilename: name,
		mutex:        new(sync.RWMutex),
	}
	if err := w.open(); err != nil {
		return nil, err
	}
	return w, nil
}

func (w *FileWriter) open() error {
	name := preprocessFilename(w.BaseFilename)
	file, err := os.OpenFile(name, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666)
	if err != nil {
		return fmt.Errorf("TIMBER! Can't open %v: %v", name, err)
	}

	var output io.WriteCloser = file
	// Wrap in gz writer
	if strings.HasSuffix(name, ".gz") {
		output = &gzFileWriter{
			gzip.NewWriter(output),
			output,
		}
	}

	w.mutex.Lock()
	defer w.mutex.Unlock()
	if w.wr != nil {
		w.wr.Close()
	}
	w.currentFilename = name
	w.wr, _ = NewBufferedWriter(output)

	return nil
}

func (w *FileWriter) LogWrite(m string) {
	w.mutex.RLock()
	defer w.mutex.RUnlock()
	w.wr.LogWrite(m)
}

func (w *FileWriter) Flush() error {
	w.mutex.RLock()
	defer w.mutex.RUnlock()
	return w.wr.Flush()
}

func (w *FileWriter) Rotate() error {
	return w.open()
}

func (w *FileWriter) Close() {
	w.mutex.Lock()
	defer w.mutex.Unlock()
	w.wr.Close()
	w.wr = nil
}

type gzFileWriter struct {
	*gzip.Writer // the compressor
	file         io.WriteCloser
}

func (w *gzFileWriter) Close() error {
	w.Writer.Close()
	return w.file.Close()
}
<commit_msg>autorotate every time.Duration<commit_after>package timber

import (
	"bytes"
	"compress/gzip"
	"fmt"
	"io"
	"os"
	"strings"
	"sync"
	"text/template"
	"time"
)

type FilenameFields struct {
	Hostname string
	Date     time.Time
	Pid      int
}

func GetFilenameFields() *FilenameFields {
	h, _ := os.Hostname()
	return &FilenameFields{
		Hostname: h,
		Date:     time.Now(),
		Pid:      os.Getpid(),
	}
}

func preprocessFilename(name string) string {
	t := template.Must(template.New("filename").Parse(name))
	buf := new(bytes.Buffer)
	t.Execute(buf, GetFilenameFields())
	return buf.String()
}

type FileWriter struct {
	wr              *BufferedWriter
	BaseFilename    string
	currentFilename string
	mutex           *sync.RWMutex
	RotateChan      chan string // defaults to nil.  receives previous filename on rotate

	rotateTicker *time.Ticker
	rotateReset  chan int
}

// This writer has a buffer that I don't ever bother to flush, so it may take a while
// to see messages.  Filenames ending in .gz will automatically be compressed on write.
// Filename string is proccessed through the template library using the FilenameFields
// struct.
func NewFileWriter(name string) (*FileWriter, error) {
	w := &FileWriter{
		BaseFilename: name,
		mutex:        new(sync.RWMutex),
	}
	if err := w.open(); err != nil {
		return nil, err
	}
	return w, nil
}

func (w *FileWriter) open() error {
	name := preprocessFilename(w.BaseFilename)
	file, err := os.OpenFile(name, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666)
	if err != nil {
		return fmt.Errorf("TIMBER! Can't open %v: %v", name, err)
	}

	var output io.WriteCloser = file
	// Wrap in gz writer
	if strings.HasSuffix(name, ".gz") {
		output = &gzFileWriter{
			gzip.NewWriter(output),
			output,
		}
	}

	w.mutex.Lock()
	defer w.mutex.Unlock()
	if w.wr != nil {
		w.wr.Close()
		// send previous filename on rotate chan
		if c := w.RotateChan; c != nil {
			c <- w.currentFilename
		}
	}
	w.currentFilename = name
	w.wr, _ = NewBufferedWriter(output)

	return nil
}

func (w *FileWriter) LogWrite(m string) {
	w.mutex.RLock()
	defer w.mutex.RUnlock()
	w.wr.LogWrite(m)
}

func (w *FileWriter) Flush() error {
	w.mutex.RLock()
	defer w.mutex.RUnlock()
	return w.wr.Flush()
}

// Close and re-open the file.
// You should use the timestamp in the filename if you're going to use rotation
func (w *FileWriter) Rotate() error {
	return w.open()
}

// Automatically rotate every `d`
func (w *FileWriter) RotateEvery(d time.Duration) {
	// reset ticker
	w.mutex.Lock()
	if w.rotateTicker != nil {
		w.rotateTicker.Stop()
		w.rotateReset <- 1
	}
	w.rotateTicker = time.NewTicker(d)
	w.mutex.Unlock()

	// trigger a rotate every X
	go func() {
		for {
			select {
			case <-w.rotateReset:
				return
			case <-w.rotateTicker.C:
				w.Rotate()
			}
		}
	}()
}

func (w *FileWriter) Close() {
	w.mutex.Lock()
	defer w.mutex.Unlock()
	w.wr.Close()
	w.wr = nil
}

type gzFileWriter struct {
	*gzip.Writer // the compressor
	file         io.WriteCloser
}

func (w *gzFileWriter) Close() error {
	w.Writer.Close()
	return w.file.Close()
}
<|endoftext|>


<commit_before>package films

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"strings"

	"github.com/citruspi/wintergarten/configuration"
	"github.com/fzzy/radix/redis"
)

type Film struct {
	Adult             bool `json:"adult"`
	AlternativeTitles struct {
		Titles []struct {
			Iso31661 string `json:"iso_3166_1"`
			Title    string `json:"title"`
		} `json:"titles"`
	} `json:"alternative_titles"`
	Availability        *FilmAvailability `json:"availability,omitempty"`
	BackdropPath        string            `json:"backdrop_path"`
	BelongsToCollection struct {
		BackdropPath string `json:"backdrop_path"`
		ID           int    `json:"id"`
		Name         string `json:"name"`
		PosterPath   string `json:"poster_path"`
	} `json:"belongs_to_collection"`
	Budget  int `json:"budget"`
	Credits struct {
		Cast []struct {
			CastID      int    `json:"cast_id"`
			Character   string `json:"character"`
			CreditID    string `json:"credit_id"`
			ID          int    `json:"id"`
			Name        string `json:"name"`
			Order       int    `json:"order"`
			ProfilePath string `json:"profile_path"`
		} `json:"cast"`
		Crew []struct {
			CreditID    string `json:"credit_id"`
			Department  string `json:"department"`
			ID          int    `json:"id"`
			Job         string `json:"job"`
			Name        string `json:"name"`
			ProfilePath string `json:"profile_path"`
		} `json:"crew"`
	} `json:"credits"`
	Genres []struct {
		ID   int    `json:"id"`
		Name string `json:"name"`
	} `json:"genres"`
	Homepage string `json:"homepage"`
	ID       int    `json:"id"`
	Images   struct {
		Backdrops []struct {
			AspectRatio float64     `json:"aspect_ratio"`
			FilePath    string      `json:"file_path"`
			Height      int         `json:"height"`
			Iso6391     interface{} `json:"iso_639_1"`
			VoteAverage float64     `json:"vote_average"`
			VoteCount   int         `json:"vote_count"`
			Width       int         `json:"width"`
		} `json:"backdrops"`
		Posters []struct {
			AspectRatio float64 `json:"aspect_ratio"`
			FilePath    string  `json:"file_path"`
			Height      int     `json:"height"`
			ID          string  `json:"id"`
			Iso6391     string  `json:"iso_639_1"`
			VoteAverage float64 `json:"vote_average"`
			VoteCount   int     `json:"vote_count"`
			Width       int     `json:"width"`
		} `json:"posters"`
	} `json:"images"`
	ImdbID   string `json:"imdb_id"`
	Keywords struct {
		Keywords []struct {
			ID   int    `json:"id"`
			Name string `json:"name"`
		} `json:"keywords"`
	} `json:"keywords"`
	OriginalLanguage    string  `json:"original_language"`
	OriginalTitle       string  `json:"original_title"`
	Overview            string  `json:"overview"`
	Popularity          float64 `json:"popularity"`
	PosterPath          string  `json:"poster_path"`
	ProductionCompanies []struct {
		ID   int    `json:"id"`
		Name string `json:"name"`
	} `json:"production_companies"`
	ProductionCountries []struct {
		Iso31661 string `json:"iso_3166_1"`
		Name     string `json:"name"`
	} `json:"production_countries"`
	ReleaseDate string `json:"release_date"`
	Releases    struct {
		Countries []struct {
			Certification string `json:"certification"`
			Iso31661      string `json:"iso_3166_1"`
			Primary       bool   `json:"primary"`
			ReleaseDate   string `json:"release_date"`
		} `json:"countries"`
	} `json:"releases"`
	Revenue int `json:"revenue"`
	Runtime int `json:"runtime"`
	Similar struct {
		Page    int `json:"page"`
		Results []struct {
			Adult         bool    `json:"adult"`
			BackdropPath  string  `json:"backdrop_path"`
			ID            int     `json:"id"`
			OriginalTitle string  `json:"original_title"`
			Popularity    float64 `json:"popularity"`
			PosterPath    string  `json:"poster_path"`
			ReleaseDate   string  `json:"release_date"`
			Title         string  `json:"title"`
			Video         bool    `json:"video"`
			VoteAverage   float64 `json:"vote_average"`
			VoteCount     int     `json:"vote_count"`
		} `json:"results"`
		TotalPages   int `json:"total_pages"`
		TotalResults int `json:"total_results"`
	} `json:"similar"`
	SpokenLanguages []struct {
		Iso6391 string `json:"iso_639_1"`
		Name    string `json:"name"`
	} `json:"spoken_languages"`
	Status  string `json:"status"`
	Tagline string `json:"tagline"`
	Title   string `json:"title"`
	Video   bool   `json:"video"`
	Videos  struct {
		Results []struct {
			ID      string `json:"id"`
			Iso6391 string `json:"iso_639_1"`
			Key     string `json:"key"`
			Name    string `json:"name"`
			Site    string `json:"site"`
			Size    int    `json:"size"`
			Type    string `json:"type"`
		} `json:"results"`
	} `json:"videos"`
	VoteAverage float64 `json:"vote_average"`
	VoteCount   int     `json:"vote_count"`
}

type FilmAvailability struct {
	Purchase *struct {
		AmazonVideo *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"amazon_video_purchase,omitempty"`
		GooglePlay *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"android_purchase,omitempty"`
		AppleiTunes *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"apple_itunes_purchase,omitempty"`
		SonyEntertainment *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"sony_purchase,omitempty"`
		Vudu *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"vudu_purchase,omitempty"`
		Youtube *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"youtube_purchase,omitempty"`
		XBOXMarketplace *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"xbox_purchase,omitempty"`
	} `json:"purchase,omitempty"`
	Rental *struct {
		AmazonVideo *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"amazon_video_rental,omitempty"`
		GooglePlay *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"android_rental,omitempty"`
		AppleiTunes *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"apple_itunes_rental,omitempty"`
		SonyEntertainment *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"sony_rental,omitempty"`
		Vudu *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"vudu_rental,omitempty"`
		Youtube *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"youtube_rental,omitempty"`
	} `json:"rental,omitempty"`
	Streaming *struct {
		NetflixInstant *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"netflix_instant,omitempty"`
	} `json:"streaming,omitempty"`
}

var (
	conf configuration.Configuration
)

func init() {
	conf = configuration.Init()
}

func Get(film_id string) (Film, error) {
	var film Film

	client, err := redis.Dial("tcp", conf.Cache.Address)

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

	defer client.Close()

	err = client.Cmd("PING").Err
	if err != nil {
		log.Fatal(err)
	}

	s, err := client.Cmd("get", film_id).Str()

	if err == nil {
		err = json.Unmarshal([]byte(s), &film)

		if err == nil {
			return film, nil
		}
	}

	film, err = queryTMDb(film_id)

	if err != nil {
		return film, err
	}

	availability, err := determineAvailability(film)

	if err == nil {
		film.Availability = &availability
	}

	marshalled, err := json.Marshal(film)

	if err == nil {
		r := client.Cmd("set", film_id, marshalled)
		if r.Err == nil {
			_ = client.Cmd("expire", film_id, conf.Cache.TTL)
		}
	}

	return film, nil
}

func Prepare(film_id string) {
	var film Film

	client, err := redis.Dial("tcp", conf.Cache.Address)

	if err != nil {
		return
	}

	defer client.Close()

	err = client.Cmd("PING").Err
	if err != nil {
		return
	}

	_, err = client.Cmd("get", film_id).Str()

	if err == nil {
		return
	}

	film, err = queryTMDb(film_id)

	if err != nil {
		return
	}

	availability, err := determineAvailability(film)

	if err == nil {
		film.Availability = &availability
	}

	marshalled, err := json.Marshal(film)

	if err == nil {
		r := client.Cmd("set", film_id, marshalled)
		if r.Err == nil {
			_ = client.Cmd("expire", film_id, conf.Cache.TTL)
		}
	}

	return
}

func queryTMDb(film_id string) (Film, error) {
	var url string
	var film Film

	var buffer bytes.Buffer

	buffer.WriteString("http://api.themoviedb.org/3/movie/")
	buffer.WriteString(film_id)
	buffer.WriteString("?api_key=")
	buffer.WriteString(conf.TMDb.APIKEY)
	buffer.WriteString("&append_to_response=")
	buffer.WriteString("alternative_titles,credits,images,keywords,releases,videos,similar")

	url = string(buffer.Bytes())

	resp, err := http.Get(url)

	if err != nil {
		return film, err
	}

	defer resp.Body.Close()

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

	if err != nil {
		return film, err
	}

	err = json.Unmarshal(body, &film)

	if err != nil {
		return film, err
	}

	return film, nil
}

func determineAvailability(film Film) (FilmAvailability, error) {
	type searchResult struct {
		Id    string `json:"_id"`
		Links struct {
			IMDb string `json:"imdb"`
		} `json:"links"`
	}

	var availability FilmAvailability
	var results []searchResult

	var cisi_url *url.URL

	cisi_url, err := url.Parse("http://www.canistream.it/services/search")

	if err != nil {
		return availability, err
	}

	parameters := url.Values{}
	parameters.Add("movieName", film.Title)

	cisi_url.RawQuery = parameters.Encode()

	resp, err := http.Get(cisi_url.String())

	if err != nil {
		return availability, err
	}

	defer resp.Body.Close()

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

	if err != nil {
		return availability, err
	}

	err = json.Unmarshal(body, &results)

	if err != nil {
		return availability, err
	}

	var canIStreamItID string

	for _, result := range results {
		var imdb_id string

		if result.Links.IMDb != "" {
			imdb_id = strings.Split(result.Links.IMDb, "/")[4]
		}

		if imdb_id == film.ImdbID {
			canIStreamItID = result.Id
		}
	}

	if canIStreamItID != "" {
		mediaTypes := []string{"rental", "purchase", "streaming"}

		for _, mediaType := range mediaTypes {
			var buffer bytes.Buffer

			buffer.WriteString("http://www.canistream.it/services/query?movieId=")
			buffer.WriteString(canIStreamItID)
			buffer.WriteString("&attributes=1&mediaType=")
			buffer.WriteString(mediaType)

			resp, err = http.Get(string(buffer.Bytes()))

			if err != nil {
				return availability, err
			}

			defer resp.Body.Close()

			body, err = ioutil.ReadAll(resp.Body)

			if err != nil {
				return availability, err
			}

			switch mediaType {
			case "rental":
				err = json.Unmarshal(body, &availability.Rental)

				if err != nil {
					return availability, err
				}
			case "purchase":
				err = json.Unmarshal(body, &availability.Purchase)

				if err != nil {
					return availability, err
				}
			case "streaming":
				err = json.Unmarshal(body, &availability.Streaming)

				if err != nil {
					return availability, err
				}
			}
		}
	}

	return availability, nil
}
<commit_msg>Disabled use of the cache if conf.Cache.Enabled is false<commit_after>package films

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"strings"

	"github.com/citruspi/wintergarten/configuration"
	"github.com/fzzy/radix/redis"
)

type Film struct {
	Adult             bool `json:"adult"`
	AlternativeTitles struct {
		Titles []struct {
			Iso31661 string `json:"iso_3166_1"`
			Title    string `json:"title"`
		} `json:"titles"`
	} `json:"alternative_titles"`
	Availability        *FilmAvailability `json:"availability,omitempty"`
	BackdropPath        string            `json:"backdrop_path"`
	BelongsToCollection struct {
		BackdropPath string `json:"backdrop_path"`
		ID           int    `json:"id"`
		Name         string `json:"name"`
		PosterPath   string `json:"poster_path"`
	} `json:"belongs_to_collection"`
	Budget  int `json:"budget"`
	Credits struct {
		Cast []struct {
			CastID      int    `json:"cast_id"`
			Character   string `json:"character"`
			CreditID    string `json:"credit_id"`
			ID          int    `json:"id"`
			Name        string `json:"name"`
			Order       int    `json:"order"`
			ProfilePath string `json:"profile_path"`
		} `json:"cast"`
		Crew []struct {
			CreditID    string `json:"credit_id"`
			Department  string `json:"department"`
			ID          int    `json:"id"`
			Job         string `json:"job"`
			Name        string `json:"name"`
			ProfilePath string `json:"profile_path"`
		} `json:"crew"`
	} `json:"credits"`
	Genres []struct {
		ID   int    `json:"id"`
		Name string `json:"name"`
	} `json:"genres"`
	Homepage string `json:"homepage"`
	ID       int    `json:"id"`
	Images   struct {
		Backdrops []struct {
			AspectRatio float64     `json:"aspect_ratio"`
			FilePath    string      `json:"file_path"`
			Height      int         `json:"height"`
			Iso6391     interface{} `json:"iso_639_1"`
			VoteAverage float64     `json:"vote_average"`
			VoteCount   int         `json:"vote_count"`
			Width       int         `json:"width"`
		} `json:"backdrops"`
		Posters []struct {
			AspectRatio float64 `json:"aspect_ratio"`
			FilePath    string  `json:"file_path"`
			Height      int     `json:"height"`
			ID          string  `json:"id"`
			Iso6391     string  `json:"iso_639_1"`
			VoteAverage float64 `json:"vote_average"`
			VoteCount   int     `json:"vote_count"`
			Width       int     `json:"width"`
		} `json:"posters"`
	} `json:"images"`
	ImdbID   string `json:"imdb_id"`
	Keywords struct {
		Keywords []struct {
			ID   int    `json:"id"`
			Name string `json:"name"`
		} `json:"keywords"`
	} `json:"keywords"`
	OriginalLanguage    string  `json:"original_language"`
	OriginalTitle       string  `json:"original_title"`
	Overview            string  `json:"overview"`
	Popularity          float64 `json:"popularity"`
	PosterPath          string  `json:"poster_path"`
	ProductionCompanies []struct {
		ID   int    `json:"id"`
		Name string `json:"name"`
	} `json:"production_companies"`
	ProductionCountries []struct {
		Iso31661 string `json:"iso_3166_1"`
		Name     string `json:"name"`
	} `json:"production_countries"`
	ReleaseDate string `json:"release_date"`
	Releases    struct {
		Countries []struct {
			Certification string `json:"certification"`
			Iso31661      string `json:"iso_3166_1"`
			Primary       bool   `json:"primary"`
			ReleaseDate   string `json:"release_date"`
		} `json:"countries"`
	} `json:"releases"`
	Revenue int `json:"revenue"`
	Runtime int `json:"runtime"`
	Similar struct {
		Page    int `json:"page"`
		Results []struct {
			Adult         bool    `json:"adult"`
			BackdropPath  string  `json:"backdrop_path"`
			ID            int     `json:"id"`
			OriginalTitle string  `json:"original_title"`
			Popularity    float64 `json:"popularity"`
			PosterPath    string  `json:"poster_path"`
			ReleaseDate   string  `json:"release_date"`
			Title         string  `json:"title"`
			Video         bool    `json:"video"`
			VoteAverage   float64 `json:"vote_average"`
			VoteCount     int     `json:"vote_count"`
		} `json:"results"`
		TotalPages   int `json:"total_pages"`
		TotalResults int `json:"total_results"`
	} `json:"similar"`
	SpokenLanguages []struct {
		Iso6391 string `json:"iso_639_1"`
		Name    string `json:"name"`
	} `json:"spoken_languages"`
	Status  string `json:"status"`
	Tagline string `json:"tagline"`
	Title   string `json:"title"`
	Video   bool   `json:"video"`
	Videos  struct {
		Results []struct {
			ID      string `json:"id"`
			Iso6391 string `json:"iso_639_1"`
			Key     string `json:"key"`
			Name    string `json:"name"`
			Site    string `json:"site"`
			Size    int    `json:"size"`
			Type    string `json:"type"`
		} `json:"results"`
	} `json:"videos"`
	VoteAverage float64 `json:"vote_average"`
	VoteCount   int     `json:"vote_count"`
}

type FilmAvailability struct {
	Purchase *struct {
		AmazonVideo *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"amazon_video_purchase,omitempty"`
		GooglePlay *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"android_purchase,omitempty"`
		AppleiTunes *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"apple_itunes_purchase,omitempty"`
		SonyEntertainment *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"sony_purchase,omitempty"`
		Vudu *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"vudu_purchase,omitempty"`
		Youtube *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"youtube_purchase,omitempty"`
		XBOXMarketplace *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"xbox_purchase,omitempty"`
	} `json:"purchase,omitempty"`
	Rental *struct {
		AmazonVideo *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"amazon_video_rental,omitempty"`
		GooglePlay *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"android_rental,omitempty"`
		AppleiTunes *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"apple_itunes_rental,omitempty"`
		SonyEntertainment *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"sony_rental,omitempty"`
		Vudu *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"vudu_rental,omitempty"`
		Youtube *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"youtube_rental,omitempty"`
	} `json:"rental,omitempty"`
	Streaming *struct {
		NetflixInstant *struct {
			URL     string `json:"direct_url,omitempty"`
			Checked int64  `json:"date_checked,omitempty"`
		} `json:"netflix_instant,omitempty"`
	} `json:"streaming,omitempty"`
}

var (
	conf configuration.Configuration
)

func init() {
	conf = configuration.Init()
}

func Get(film_id string) (Film, error) {
	var film Film

	if conf.Cache.Enabled {
		client, err := redis.Dial("tcp", conf.Cache.Address)

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

		defer client.Close()

		err = client.Cmd("PING").Err
		if err != nil {
			log.Fatal(err)
		}

		s, err := client.Cmd("get", film_id).Str()

		if err == nil {
			err = json.Unmarshal([]byte(s), &film)

			if err == nil {
				return film, nil
			}
		}
	}

	film, err := queryTMDb(film_id)

	if err != nil {
		return film, err
	}

	availability, err := determineAvailability(film)

	if err == nil {
		film.Availability = &availability
	}

	marshalled, err := json.Marshal(film)

	if err == nil {
		if conf.Cache.Enabled {
			client, err := redis.Dial("tcp", conf.Cache.Address)

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

			defer client.Close()

			err = client.Cmd("PING").Err
			if err != nil {
				log.Fatal(err)
			}

			r := client.Cmd("set", film_id, marshalled)
			if r.Err == nil {
				_ = client.Cmd("expire", film_id, conf.Cache.TTL)
			}
		}
	}

	return film, nil
}

func Prepare(film_id string) {
	if !conf.Cache.Enabled {
		return
	}

	var film Film

	client, err := redis.Dial("tcp", conf.Cache.Address)

	if err != nil {
		return
	}

	defer client.Close()

	err = client.Cmd("PING").Err
	if err != nil {
		return
	}

	_, err = client.Cmd("get", film_id).Str()

	if err == nil {
		return
	}

	film, err = queryTMDb(film_id)

	if err != nil {
		return
	}

	availability, err := determineAvailability(film)

	if err == nil {
		film.Availability = &availability
	}

	marshalled, err := json.Marshal(film)

	if err == nil {
		r := client.Cmd("set", film_id, marshalled)
		if r.Err == nil {
			_ = client.Cmd("expire", film_id, conf.Cache.TTL)
		}
	}

	return
}

func queryTMDb(film_id string) (Film, error) {
	var url string
	var film Film

	var buffer bytes.Buffer

	buffer.WriteString("http://api.themoviedb.org/3/movie/")
	buffer.WriteString(film_id)
	buffer.WriteString("?api_key=")
	buffer.WriteString(conf.TMDb.APIKEY)
	buffer.WriteString("&append_to_response=")
	buffer.WriteString("alternative_titles,credits,images,keywords,releases,videos,similar")

	url = string(buffer.Bytes())

	resp, err := http.Get(url)

	if err != nil {
		return film, err
	}

	defer resp.Body.Close()

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

	if err != nil {
		return film, err
	}

	err = json.Unmarshal(body, &film)

	if err != nil {
		return film, err
	}

	return film, nil
}

func determineAvailability(film Film) (FilmAvailability, error) {
	type searchResult struct {
		Id    string `json:"_id"`
		Links struct {
			IMDb string `json:"imdb"`
		} `json:"links"`
	}

	var availability FilmAvailability
	var results []searchResult

	var cisi_url *url.URL

	cisi_url, err := url.Parse("http://www.canistream.it/services/search")

	if err != nil {
		return availability, err
	}

	parameters := url.Values{}
	parameters.Add("movieName", film.Title)

	cisi_url.RawQuery = parameters.Encode()

	resp, err := http.Get(cisi_url.String())

	if err != nil {
		return availability, err
	}

	defer resp.Body.Close()

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

	if err != nil {
		return availability, err
	}

	err = json.Unmarshal(body, &results)

	if err != nil {
		return availability, err
	}

	var canIStreamItID string

	for _, result := range results {
		var imdb_id string

		if result.Links.IMDb != "" {
			imdb_id = strings.Split(result.Links.IMDb, "/")[4]
		}

		if imdb_id == film.ImdbID {
			canIStreamItID = result.Id
		}
	}

	if canIStreamItID != "" {
		mediaTypes := []string{"rental", "purchase", "streaming"}

		for _, mediaType := range mediaTypes {
			var buffer bytes.Buffer

			buffer.WriteString("http://www.canistream.it/services/query?movieId=")
			buffer.WriteString(canIStreamItID)
			buffer.WriteString("&attributes=1&mediaType=")
			buffer.WriteString(mediaType)

			resp, err = http.Get(string(buffer.Bytes()))

			if err != nil {
				return availability, err
			}

			defer resp.Body.Close()

			body, err = ioutil.ReadAll(resp.Body)

			if err != nil {
				return availability, err
			}

			switch mediaType {
			case "rental":
				err = json.Unmarshal(body, &availability.Rental)

				if err != nil {
					return availability, err
				}
			case "purchase":
				err = json.Unmarshal(body, &availability.Purchase)

				if err != nil {
					return availability, err
				}
			case "streaming":
				err = json.Unmarshal(body, &availability.Streaming)

				if err != nil {
					return availability, err
				}
			}
		}
	}

	return availability, nil
}
<|endoftext|>


<commit_before>package ldap

import (
	"testing"

	"github.com/vanackere/asn1-ber"
)

type compileTest struct {
	filterStr  string
	filterType int
}

var testFilters = []compileTest{
	compileTest{filterStr: "(&(sn=Miller)(givenName=Bob))", filterType: FilterAnd},
	compileTest{filterStr: "(|(sn=Miller)(givenName=Bob))", filterType: FilterOr},
	compileTest{filterStr: "(!(sn=Miller))", filterType: FilterNot},
	compileTest{filterStr: "(sn=Miller)", filterType: FilterEqualityMatch},
	compileTest{filterStr: "(sn=Mill*)", filterType: FilterSubstrings},
	compileTest{filterStr: "(sn=*Mill)", filterType: FilterSubstrings},
	compileTest{filterStr: "(sn=*Mill*)", filterType: FilterSubstrings},
	compileTest{filterStr: "(sn>=Miller)", filterType: FilterGreaterOrEqual},
	compileTest{filterStr: "(sn<=Miller)", filterType: FilterLessOrEqual},
	compileTest{filterStr: "(sn=*)", filterType: FilterPresent},
	compileTest{filterStr: "(sn~=Miller)", filterType: FilterApproxMatch},
	// compileTest{ filterStr: "()", filterType: FilterExtensibleMatch },
}

func TestFilter(t *testing.T) {
	// Test Compiler and Decompiler
	for _, i := range testFilters {
		filter, err := CompileFilter(i.filterStr)
		if err != nil {
			t.Errorf("Problem compiling %s - %s", i.filterStr, err.Error())
		} else if filter.Tag != uint8(i.filterType) {
			t.Errorf("%q Expected %q got %q", i.filterStr, FilterMap[uint64(i.filterType)], FilterMap[uint64(filter.Tag)])
		} else {
			o, err := DecompileFilter(filter)
			if err != nil {
				t.Errorf("Problem compiling %s - %s", i.filterStr, err.Error())
			} else if i.filterStr != o {
				t.Errorf("%q expected, got %q", i.filterStr, o)
			}
		}
	}
}

func TestDecodePresentFilter(t *testing.T) {

	bs := []byte{0x87, 0x06, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72} // ~ (member=*)

	p := ber.DecodePacket(bs)
	f, err := DecompileFilter(p)
	if err != nil {
		t.Errorf("--- CAN'T DECODE & DECOMPILE FILTER")
		return
	}
	if f != "(member=*)" {
		t.Errorf("expected (member=*), got %s", f)
	}

}

func TestEncodeDecodePresentFilter(t *testing.T) {
	f := "(member=*)"
	p, err := CompileFilter(f)
	if err != nil {
		t.Errorf("cant compile filter")
		return
	}
	bytes := p.Bytes()

	p2 := ber.DecodePacket(bytes)
	f2, err := DecompileFilter(p2)
	if err != nil {
		t.Errorf("cant decompile filter")
		return
	}
	if f != f2 {
		t.Errorf("encode/decode changed filter")
	}
}

func BenchmarkFilterCompile(b *testing.B) {
	b.StopTimer()
	filters := make([]string, len(testFilters))

	// Test Compiler and Decompiler
	for idx, i := range testFilters {
		filters[idx] = i.filterStr
	}

	maxIdx := len(filters)
	b.StartTimer()
	for i := 0; i < b.N; i++ {
		CompileFilter(filters[i%maxIdx])
	}
}

func BenchmarkFilterDecompile(b *testing.B) {
	b.StopTimer()
	filters := make([]*ber.Packet, len(testFilters))

	// Test Compiler and Decompiler
	for idx, i := range testFilters {
		filters[idx], _ = CompileFilter(i.filterStr)
	}

	maxIdx := len(filters)
	b.StartTimer()
	for i := 0; i < b.N; i++ {
		DecompileFilter(filters[i%maxIdx])
	}
}
<commit_msg>filter_test : rewrite encode/decode tests to be table-driven<commit_after>package ldap

import (
	"reflect"
	"testing"

	"github.com/vanackere/asn1-ber"
)

type compileTest struct {
	filterStr  string
	filterType int
}

var testFilters = []compileTest{
	compileTest{filterStr: "(&(sn=Miller)(givenName=Bob))", filterType: FilterAnd},
	compileTest{filterStr: "(|(sn=Miller)(givenName=Bob))", filterType: FilterOr},
	compileTest{filterStr: "(!(sn=Miller))", filterType: FilterNot},
	compileTest{filterStr: "(sn=Miller)", filterType: FilterEqualityMatch},
	compileTest{filterStr: "(sn=Mill*)", filterType: FilterSubstrings},
	compileTest{filterStr: "(sn=*Mill)", filterType: FilterSubstrings},
	compileTest{filterStr: "(sn=*Mill*)", filterType: FilterSubstrings},
	compileTest{filterStr: "(sn>=Miller)", filterType: FilterGreaterOrEqual},
	compileTest{filterStr: "(sn<=Miller)", filterType: FilterLessOrEqual},
	compileTest{filterStr: "(sn=*)", filterType: FilterPresent},
	compileTest{filterStr: "(sn~=Miller)", filterType: FilterApproxMatch},
	// compileTest{ filterStr: "()", filterType: FilterExtensibleMatch },
}

func TestFilter(t *testing.T) {
	// Test Compiler and Decompiler
	for _, i := range testFilters {
		filter, err := CompileFilter(i.filterStr)
		if err != nil {
			t.Errorf("Problem compiling %s - %s", i.filterStr, err.Error())
		} else if filter.Tag != uint8(i.filterType) {
			t.Errorf("%q Expected %q got %q", i.filterStr, FilterMap[uint64(i.filterType)], FilterMap[uint64(filter.Tag)])
		} else {
			o, err := DecompileFilter(filter)
			if err != nil {
				t.Errorf("Problem compiling %s - %s", i.filterStr, err.Error())
			} else if i.filterStr != o {
				t.Errorf("%q expected, got %q", i.filterStr, o)
			}
		}
	}
}

type binTestFilter struct {
	bin []byte
	str string
}

var binTestFilters = []binTestFilter{
	{bin: []byte{0x87, 0x06, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72}, str: "(member=*)"},
}

func TestFiltersDecode(t *testing.T) {
	for i, test := range binTestFilters {
		p := ber.DecodePacket(test.bin)
		if filter, err := DecompileFilter(p); err != nil {
			t.Errorf("binTestFilters[%d], DecompileFilter returned : %s", i, err)
		} else if filter != test.str {
			t.Errorf("binTestFilters[%d], %q expected, got %q", i, test.str, filter)
		}
	}
}

func TestFiltersEncode(t *testing.T) {
	for i, test := range binTestFilters {
		p, err := CompileFilter(test.str)
		if err != nil {
			t.Errorf("binTestFilters[%d], CompileFilter returned : %s", i, err)
			continue
		}
		b := p.Bytes()
		if !reflect.DeepEqual(b, test.bin) {
			t.Errorf("binTestFilters[%d], %q expected for CompileFilter(%q), got %q", i, test.bin, test.str, b)
		}
	}
}

func BenchmarkFilterCompile(b *testing.B) {
	b.StopTimer()
	filters := make([]string, len(testFilters))

	// Test Compiler and Decompiler
	for idx, i := range testFilters {
		filters[idx] = i.filterStr
	}

	maxIdx := len(filters)
	b.StartTimer()
	for i := 0; i < b.N; i++ {
		CompileFilter(filters[i%maxIdx])
	}
}

func BenchmarkFilterDecompile(b *testing.B) {
	b.StopTimer()
	filters := make([]*ber.Packet, len(testFilters))

	// Test Compiler and Decompiler
	for idx, i := range testFilters {
		filters[idx], _ = CompileFilter(i.filterStr)
	}

	maxIdx := len(filters)
	b.StartTimer()
	for i := 0; i < b.N; i++ {
		DecompileFilter(filters[i%maxIdx])
	}
}
<|endoftext|>


<commit_before>// Copyright 2014 ALTOROS
// Licensed under the AGPLv3, see LICENSE file for details.

package gosigma

import (
	"fmt"

	"github.com/Altoros/gosigma/data"
)

const (
	// DriveUnmounted defines constant for unmounted drive status
	DriveUnmounted = "unmounted"
	// DriveCreating defines constant for creating drive status
	DriveCreating = "creating"
	// DriveResizing defines constant for resizing drive status
	DriveResizing = "resizing"
	// DriveCloningDst defines constant for drive cloning status
	DriveCloningDst = "cloning_dst"
	// ... may be another values here, contact CloudSigma devs
)

const (
	// MediaCdrom defines media type for cdrom drives
	MediaCdrom = "cdrom"
	// MediaDisk defines media type for disk drives
	MediaDisk = "disk"
)

// A Drive interface represents drive instance in CloudSigma account
type Drive interface {
	// CloudSigma resource
	Resource

	// Get meta-information value stored in the drive instance
	Get(key string) (v string, ok bool)

	// Media of drive instance
	Media() string

	// Name of drive instance
	Name() string

	// Size of drive in bytes
	Size() int64

	// Status of drive instance
	Status() string

	// StorageType of drive instance
	StorageType() string

	// IsLibrary returns true if this drive is CloudSigma library drive
	Library() LibrarySpec

	// OS returns operating system of the drive (defined for library drives)
	OS() string

	// Arch returns operating system bit architecture the drive (defined for library drives)
	Arch() string

	// Paid image or free (defined for library drives)
	Paid() bool

	// ImageType returns type of drive image (defined for library drives)
	ImageType() string

	// Clone drive instance
	Clone(params CloneParams, avoid []string) (Drive, error)

	// Clone drive instance, wait for operation finished
	CloneWait(params CloneParams, avoid []string) (Drive, error)

	// Jobs for this drive instance.
	// Every job object in resulting slice carries only UUID and URI.
	// To obtain additional information for job, one should use Job.Refresh() method
	// to query cloud for detailed job information.
	Jobs() []Job

	// Refresh information about drive instance
	Refresh() error

	// Resize drive instance
	Resize(newSize uint64) error

	// Resize drive instance, wait for operation finished
	ResizeWait(newSize uint64) error
}

// A drive implements drive instance in CloudSigma account
type drive struct {
	client  *Client
	obj     *data.Drive
	library LibrarySpec
}

var _ Drive = (*drive)(nil)

// String method is used to print values passed as an operand to any format that
// accepts a string or to an unformatted printer such as Print.
func (d drive) String() string {
	return fmt.Sprintf("{Name: %q\nURI: %q\nStatus: %s\nUUID: %q\nSize: %d\nMedia: %s\nStorage: %s}",
		d.Name(), d.URI(), d.Status(), d.UUID(), d.Size(), d.Media(), d.StorageType())
}

// URI of drive instance
func (d drive) URI() string { return d.obj.URI }

// UUID of drive instance
func (d drive) UUID() string { return d.obj.UUID }

// Get meta-information value stored in the drive instance
func (d drive) Get(key string) (v string, ok bool) {
	v, ok = d.obj.Meta[key]
	return
}

// Media of drive instance
func (d drive) Media() string { return d.obj.Media }

// Name of drive instance
func (d drive) Name() string { return d.obj.Name }

// Size of drive in bytes
func (d drive) Size() int64 { return d.obj.Size }

// Status of drive instance
func (d drive) Status() string { return d.obj.Status }

// StorageType of drive instance
func (d drive) StorageType() string { return d.obj.StorageType }

// IsLibrary returns true if this drive is CloudSigma library drive
func (d drive) Library() LibrarySpec { return d.library }

// OS returns operating system of the drive (defined for library drives)
func (d drive) OS() string { return d.obj.OS }

// Arch returns operating system bit architecture the drive (defined for library drives)
func (d drive) Arch() string { return d.obj.Arch }

// Paid image or free (defined for library drives)
func (d drive) Paid() bool { return d.obj.Paid }

// ImageType returns type of drive image (defined for library drives)
func (d drive) ImageType() string { return d.obj.ImageType }

// Clone drive instance.
func (d drive) Clone(params CloneParams, avoid []string) (Drive, error) {
	obj, err := d.clone(params, avoid)
	if err != nil {
		return nil, err
	}

	newDrive := &drive{
		client:  d.client,
		obj:     obj,
		library: LibraryAccount,
	}

	return newDrive, nil
}

// Clone drive instance, wait for operation finished.
func (d drive) CloneWait(params CloneParams, avoid []string) (Drive, error) {
	obj, err := d.clone(params, avoid)
	if err != nil {
		return nil, err
	}

	newDrive := &drive{
		client:  d.client,
		obj:     obj,
		library: LibraryAccount,
	}

	jj := newDrive.Jobs()
	if len(jj) == 0 {
		return newDrive, nil
	}

	j := jj[0]

	if err := j.Wait(); err != nil {
		return nil, err
	}

	if err := newDrive.Refresh(); err != nil {
		return nil, err
	}

	if d.Library() == LibraryMedia {
		obj.LibraryDrive = d.obj.LibraryDrive
	}

	return newDrive, nil
}

// Jobs for this drive instance.
// Every job object in resulting slice carries only UUID and URI.
// To obtain additional information for job, one should use Job.Refresh() method
// to query cloud for detailed job information.
func (d drive) Jobs() []Job {
	r := make([]Job, 0, len(d.obj.Jobs))
	for _, j := range d.obj.Jobs {
		j := &job{d.client, &data.Job{Resource: j}}
		r = append(r, j)
	}
	return r
}

// Refresh information about drive instance
func (d *drive) Refresh() error {
	obj, err := d.client.getDrive(d.UUID(), d.Library())
	if err != nil {
		return err
	}
	d.obj = obj
	return nil
}

// Resize drive instance
func (d *drive) Resize(newSize uint64) error {
	return nil
}

// Resize drive instance, wait for operation finished
func (d *drive) ResizeWait(newSize uint64) error {
	return nil
}
<commit_msg>added stub for Drive.Wait<commit_after>// Copyright 2014 ALTOROS
// Licensed under the AGPLv3, see LICENSE file for details.

package gosigma

import (
	"fmt"

	"github.com/Altoros/gosigma/data"
)

const (
	// DriveUnmounted defines constant for unmounted drive status
	DriveUnmounted = "unmounted"
	// DriveCreating defines constant for creating drive status
	DriveCreating = "creating"
	// DriveResizing defines constant for resizing drive status
	DriveResizing = "resizing"
	// DriveCloningDst defines constant for drive cloning status
	DriveCloningDst = "cloning_dst"
	// ... may be another values here, contact CloudSigma devs
)

const (
	// MediaCdrom defines media type for cdrom drives
	MediaCdrom = "cdrom"
	// MediaDisk defines media type for disk drives
	MediaDisk = "disk"
)

// A Drive interface represents drive instance in CloudSigma account
type Drive interface {
	// CloudSigma resource
	Resource

	// Get meta-information value stored in the drive instance
	Get(key string) (v string, ok bool)

	// Media of drive instance
	Media() string

	// Name of drive instance
	Name() string

	// Size of drive in bytes
	Size() int64

	// Status of drive instance
	Status() string

	// StorageType of drive instance
	StorageType() string

	// IsLibrary returns true if this drive is CloudSigma library drive
	Library() LibrarySpec

	// OS returns operating system of the drive (defined for library drives)
	OS() string

	// Arch returns operating system bit architecture the drive (defined for library drives)
	Arch() string

	// Paid image or free (defined for library drives)
	Paid() bool

	// ImageType returns type of drive image (defined for library drives)
	ImageType() string

	// Clone drive instance
	Clone(params CloneParams, avoid []string) (Drive, error)

	// Clone drive instance, wait for operation finished
	CloneWait(params CloneParams, avoid []string) (Drive, error)

	// Jobs for this drive instance.
	// Every job object in resulting slice carries only UUID and URI.
	// To obtain additional information for job, one should use Job.Refresh() method
	// to query cloud for detailed job information.
	Jobs() []Job

	// Refresh information about drive instance
	Refresh() error

	// Resize drive instance
	Resize(newSize uint64) error

	// Resize drive instance, wait for operation finished
	ResizeWait(newSize uint64) error

	// Wait for user-defined event
	Wait(stop func(Drive) bool) error
}

// A drive implements drive instance in CloudSigma account
type drive struct {
	client  *Client
	obj     *data.Drive
	library LibrarySpec
}

var _ Drive = (*drive)(nil)

// String method is used to print values passed as an operand to any format that
// accepts a string or to an unformatted printer such as Print.
func (d drive) String() string {
	return fmt.Sprintf("{Name: %q\nURI: %q\nStatus: %s\nUUID: %q\nSize: %d\nMedia: %s\nStorage: %s}",
		d.Name(), d.URI(), d.Status(), d.UUID(), d.Size(), d.Media(), d.StorageType())
}

// URI of drive instance
func (d drive) URI() string { return d.obj.URI }

// UUID of drive instance
func (d drive) UUID() string { return d.obj.UUID }

// Get meta-information value stored in the drive instance
func (d drive) Get(key string) (v string, ok bool) {
	v, ok = d.obj.Meta[key]
	return
}

// Media of drive instance
func (d drive) Media() string { return d.obj.Media }

// Name of drive instance
func (d drive) Name() string { return d.obj.Name }

// Size of drive in bytes
func (d drive) Size() int64 { return d.obj.Size }

// Status of drive instance
func (d drive) Status() string { return d.obj.Status }

// StorageType of drive instance
func (d drive) StorageType() string { return d.obj.StorageType }

// IsLibrary returns true if this drive is CloudSigma library drive
func (d drive) Library() LibrarySpec { return d.library }

// OS returns operating system of the drive (defined for library drives)
func (d drive) OS() string { return d.obj.OS }

// Arch returns operating system bit architecture the drive (defined for library drives)
func (d drive) Arch() string { return d.obj.Arch }

// Paid image or free (defined for library drives)
func (d drive) Paid() bool { return d.obj.Paid }

// ImageType returns type of drive image (defined for library drives)
func (d drive) ImageType() string { return d.obj.ImageType }

// Clone drive instance.
func (d drive) Clone(params CloneParams, avoid []string) (Drive, error) {
	obj, err := d.clone(params, avoid)
	if err != nil {
		return nil, err
	}

	newDrive := &drive{
		client:  d.client,
		obj:     obj,
		library: LibraryAccount,
	}

	return newDrive, nil
}

// Clone drive instance, wait for operation finished.
func (d drive) CloneWait(params CloneParams, avoid []string) (Drive, error) {
	obj, err := d.clone(params, avoid)
	if err != nil {
		return nil, err
	}

	newDrive := &drive{
		client:  d.client,
		obj:     obj,
		library: LibraryAccount,
	}

	jj := newDrive.Jobs()
	if len(jj) == 0 {
		return newDrive, nil
	}

	j := jj[0]

	if err := j.Wait(); err != nil {
		return nil, err
	}

	if err := newDrive.Refresh(); err != nil {
		return nil, err
	}

	if d.Library() == LibraryMedia {
		obj.LibraryDrive = d.obj.LibraryDrive
	}

	return newDrive, nil
}

// Jobs for this drive instance.
// Every job object in resulting slice carries only UUID and URI.
// To obtain additional information for job, one should use Job.Refresh() method
// to query cloud for detailed job information.
func (d drive) Jobs() []Job {
	r := make([]Job, 0, len(d.obj.Jobs))
	for _, j := range d.obj.Jobs {
		j := &job{d.client, &data.Job{Resource: j}}
		r = append(r, j)
	}
	return r
}

// Refresh information about drive instance
func (d *drive) Refresh() error {
	obj, err := d.client.getDrive(d.UUID(), d.Library())
	if err != nil {
		return err
	}
	d.obj = obj
	return nil
}

// Resize drive instance
func (d *drive) Resize(newSize uint64) error {
	return nil
}

// Resize drive instance, wait for operation finished
func (d *drive) ResizeWait(newSize uint64) error {
	return nil
}

// Wait for user-defined event
func (d *drive) Wait(stop func(Drive) bool) error {
	return nil
}
<|endoftext|>


<commit_before>//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
//
// Code generated by Alibaba Cloud SDK Code Generator.
// Changes may cause incorrect behavior and will be lost if the code is regenerated.

package nas

import (
	"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
	"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
)

// DescribeZones invokes the nas.DescribeZones API synchronously
// api document: https://help.aliyun.com/api/nas/describezones.html
func (client *Client) DescribeZones(request *DescribeZonesRequest) (response *DescribeZonesResponse, err error) {
	response = CreateDescribeZonesResponse()
	err = client.DoAction(request, response)
	return
}

// DescribeZonesWithChan invokes the nas.DescribeZones API asynchronously
// api document: https://help.aliyun.com/api/nas/describezones.html
// asynchronous document: https://help.aliyun.com/document_detail/66220.html
func (client *Client) DescribeZonesWithChan(request *DescribeZonesRequest) (<-chan *DescribeZonesResponse, <-chan error) {
	responseChan := make(chan *DescribeZonesResponse, 1)
	errChan := make(chan error, 1)
	err := client.AddAsyncTask(func() {
		defer close(responseChan)
		defer close(errChan)
		response, err := client.DescribeZones(request)
		if err != nil {
			errChan <- err
		} else {
			responseChan <- response
		}
	})
	if err != nil {
		errChan <- err
		close(responseChan)
		close(errChan)
	}
	return responseChan, errChan
}

// DescribeZonesWithCallback invokes the nas.DescribeZones API asynchronously
// api document: https://help.aliyun.com/api/nas/describezones.html
// asynchronous document: https://help.aliyun.com/document_detail/66220.html
func (client *Client) DescribeZonesWithCallback(request *DescribeZonesRequest, callback func(response *DescribeZonesResponse, err error)) <-chan int {
	result := make(chan int, 1)
	err := client.AddAsyncTask(func() {
		var response *DescribeZonesResponse
		var err error
		defer close(result)
		response, err = client.DescribeZones(request)
		callback(response, err)
		result <- 1
	})
	if err != nil {
		defer close(result)
		callback(nil, err)
		result <- 0
	}
	return result
}

// DescribeZonesRequest is the request struct for api DescribeZones
type DescribeZonesRequest struct {
	*requests.RpcRequest
}

// DescribeZonesResponse is the response struct for api DescribeZones
type DescribeZonesResponse struct {
	*responses.BaseResponse
	RequestId string              `json:"RequestId" xml:"RequestId"`
	Zones     DescribeZonesZones0 `json:"Zones" xml:"Zones"`
}

type DescribeZonesZones0 struct {
	Zone []DescribeZonesZone1 `json:"Zone" xml:"Zone"`
}

type DescribeZonesZone1 struct {
	ZoneId      string   `json:"ZoneId" xml:"ZoneId"`
	Capacity    []string `json:"Capacity" xml:"Capacity"`
	Performance []string `json:"Performance" xml:"Performance"`
}

// CreateDescribeZonesRequest creates a request to invoke DescribeZones API
func CreateDescribeZonesRequest() (request *DescribeZonesRequest) {
	request = &DescribeZonesRequest{
		RpcRequest: &requests.RpcRequest{},
	}
	request.InitWithApiInfo("NAS", "2017-06-26", "DescribeZones", "nas", "openAPI")
	return
}

// CreateDescribeZonesResponse creates a response to parse from DescribeZones response
func CreateDescribeZonesResponse() (response *DescribeZonesResponse) {
	response = &DescribeZonesResponse{
		BaseResponse: &responses.BaseResponse{},
	}
	return
}
<commit_msg>fix nas service<commit_after>//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
//
// Code generated by Alibaba Cloud SDK Code Generator.
// Changes may cause incorrect behavior and will be lost if the code is regenerated.

package nas

import (
	"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
	"github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
)

// DescribeZones invokes the nas.DescribeZones API synchronously
// api document: https://help.aliyun.com/api/nas/describezones.html
func (client *Client) DescribeZones(request *DescribeZonesRequest) (response *DescribeZonesResponse, err error) {
	response = CreateDescribeZonesResponse()
	err = client.DoAction(request, response)
	return
}

// DescribeZonesWithChan invokes the nas.DescribeZones API asynchronously
// api document: https://help.aliyun.com/api/nas/describezones.html
// asynchronous document: https://help.aliyun.com/document_detail/66220.html
func (client *Client) DescribeZonesWithChan(request *DescribeZonesRequest) (<-chan *DescribeZonesResponse, <-chan error) {
	responseChan := make(chan *DescribeZonesResponse, 1)
	errChan := make(chan error, 1)
	err := client.AddAsyncTask(func() {
		defer close(responseChan)
		defer close(errChan)
		response, err := client.DescribeZones(request)
		if err != nil {
			errChan <- err
		} else {
			responseChan <- response
		}
	})
	if err != nil {
		errChan <- err
		close(responseChan)
		close(errChan)
	}
	return responseChan, errChan
}

// DescribeZonesWithCallback invokes the nas.DescribeZones API asynchronously
// api document: https://help.aliyun.com/api/nas/describezones.html
// asynchronous document: https://help.aliyun.com/document_detail/66220.html
func (client *Client) DescribeZonesWithCallback(request *DescribeZonesRequest, callback func(response *DescribeZonesResponse, err error)) <-chan int {
	result := make(chan int, 1)
	err := client.AddAsyncTask(func() {
		var response *DescribeZonesResponse
		var err error
		defer close(result)
		response, err = client.DescribeZones(request)
		callback(response, err)
		result <- 1
	})
	if err != nil {
		defer close(result)
		callback(nil, err)
		result <- 0
	}
	return result
}

// DescribeZonesRequest is the request struct for api DescribeZones
type DescribeZonesRequest struct {
	*requests.RpcRequest
}

// DescribeZonesResponse is the response struct for api DescribeZones
type DescribeZonesResponse struct {
	*responses.BaseResponse
	RequestId string              `json:"RequestId" xml:"RequestId"`
	Zones     DescribeZonesZones0 `json:"Zones" xml:"Zones"`
}

type DescribeZonesZones0 struct {
	Zone []DescribeZonesZone1 `json:"Zone" xml:"Zone"`
}

type DescribeZonesZone1 struct {
	ZoneId      string                `json:"ZoneId" xml:"ZoneId"`
	Capacity    DescribeZonesProtocol `json:"Capacity" xml:"Capacity"`
	Performance DescribeZonesProtocol `json:"Performance" xml:"Performance"`
}

type DescribeZonesProtocol struct {
	Protocol []string `json:"Protocol" xml:"Protocol"`
}

// CreateDescribeZonesRequest creates a request to invoke DescribeZones API
func CreateDescribeZonesRequest() (request *DescribeZonesRequest) {
	request = &DescribeZonesRequest{
		RpcRequest: &requests.RpcRequest{},
	}
	request.InitWithApiInfo("NAS", "2017-06-26", "DescribeZones", "nas", "openAPI")
	return
}

// CreateDescribeZonesResponse creates a response to parse from DescribeZones response
func CreateDescribeZonesResponse() (response *DescribeZonesResponse) {
	response = &DescribeZonesResponse{
		BaseResponse: &responses.BaseResponse{},
	}
	return
}
<|endoftext|>


<commit_before><commit_msg>print: restrict where empty lines are allowed<commit_after><|endoftext|>


<commit_before><commit_msg>Moved matching logic up into caller to enable search<commit_after><|endoftext|>