<commit_before>// Copyright 2016 Google Inc. All rights reserved.
//
// 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 pathtools

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"sort"
	"strings"
	"syscall"
	"time"
)

// Based on Andrew Gerrand's "10 things you (probably) dont' know about Go"

type ShouldFollowSymlinks bool

const (
	FollowSymlinks     = ShouldFollowSymlinks(true)
	DontFollowSymlinks = ShouldFollowSymlinks(false)
)

var OsFs FileSystem = osFs{}

func MockFs(files map[string][]byte) FileSystem {
	fs := &mockFs{
		files:    make(map[string][]byte, len(files)),
		dirs:     make(map[string]bool),
		symlinks: make(map[string]string),
		all:      []string(nil),
	}

	for f, b := range files {
		if tokens := strings.SplitN(f, "->", 2); len(tokens) == 2 {
			fs.symlinks[strings.TrimSpace(tokens[0])] = strings.TrimSpace(tokens[1])
			continue
		}

		fs.files[filepath.Clean(f)] = b
		dir := filepath.Dir(f)
		for dir != "." && dir != "/" {
			fs.dirs[dir] = true
			dir = filepath.Dir(dir)
		}
		fs.dirs[dir] = true
	}

	for f := range fs.files {
		fs.all = append(fs.all, f)
	}

	for d := range fs.dirs {
		fs.all = append(fs.all, d)
	}

	for s := range fs.symlinks {
		fs.all = append(fs.all, s)
	}

	sort.Strings(fs.all)

	return fs
}

type FileSystem interface {
	// Open opens a file for reading.  Follows symlinks.
	Open(name string) (io.ReadCloser, error)

	// Exists returns whether the file exists and whether it is a directory.  Follows symlinks.
	Exists(name string) (bool, bool, error)

	Glob(pattern string, excludes []string, follow ShouldFollowSymlinks) (matches, dirs []string, err error)
	glob(pattern string) (matches []string, err error)

	// IsDir returns true if the path points to a directory, false it it points to a file.  Follows symlinks.
	// Returns os.ErrNotExist if the path does not exist or is a symlink to a path that does not exist.
	IsDir(name string) (bool, error)

	// IsSymlink returns true if the path points to a symlink, even if that symlink points to a path that does
	// not exist.  Returns os.ErrNotExist if the path does not exist.
	IsSymlink(name string) (bool, error)

	// Lstat returns info on a file without following symlinks.
	Lstat(name string) (os.FileInfo, error)

	// ListDirsRecursive returns a list of all the directories in a path, following symlinks if requested.
	ListDirsRecursive(name string, follow ShouldFollowSymlinks) (dirs []string, err error)

	// ReadDirNames returns a list of everything in a directory.
	ReadDirNames(name string) ([]string, error)
}

// osFs implements FileSystem using the local disk.
type osFs struct{}

func (osFs) Open(name string) (io.ReadCloser, error) { return os.Open(name) }
func (osFs) Exists(name string) (bool, bool, error) {
	stat, err := os.Stat(name)
	if err == nil {
		return true, stat.IsDir(), nil
	} else if os.IsNotExist(err) {
		return false, false, nil
	} else {
		return false, false, err
	}
}

func (osFs) IsDir(name string) (bool, error) {
	info, err := os.Stat(name)
	if err != nil {
		return false, err
	}
	return info.IsDir(), nil
}

func (osFs) IsSymlink(name string) (bool, error) {
	if info, err := os.Lstat(name); err != nil {
		return false, err
	} else {
		return info.Mode()&os.ModeSymlink != 0, nil
	}
}

func (fs osFs) Glob(pattern string, excludes []string, follow ShouldFollowSymlinks) (matches, dirs []string, err error) {
	return startGlob(fs, pattern, excludes, follow)
}

func (osFs) glob(pattern string) ([]string, error) {
	return filepath.Glob(pattern)
}

func (osFs) Lstat(path string) (stats os.FileInfo, err error) {
	return os.Lstat(path)
}

// Returns a list of all directories under dir
func (osFs) ListDirsRecursive(name string, follow ShouldFollowSymlinks) (dirs []string, err error) {
	return listDirsRecursive(OsFs, name, follow)
}

func (osFs) ReadDirNames(name string) ([]string, error) {
	dir, err := os.Open(name)
	if err != nil {
		return nil, err
	}
	defer dir.Close()

	contents, err := dir.Readdirnames(-1)
	if err != nil {
		return nil, err
	}

	sort.Strings(contents)
	return contents, nil
}

type mockFs struct {
	files    map[string][]byte
	dirs     map[string]bool
	symlinks map[string]string
	all      []string
}

func (m *mockFs) followSymlinks(name string) string {
	dir, file := saneSplit(name)
	if dir != "." && dir != "/" {
		dir = m.followSymlinks(dir)
	}
	name = filepath.Join(dir, file)

	for i := 0; i < 255; i++ {
		i++
		if i > 255 {
			panic("symlink loop")
		}
		to, exists := m.symlinks[name]
		if !exists {
			break
		}
		if filepath.IsAbs(to) {
			name = to
		} else {
			name = filepath.Join(dir, to)
		}
	}
	return name
}

func (m *mockFs) Open(name string) (io.ReadCloser, error) {
	name = filepath.Clean(name)
	name = m.followSymlinks(name)
	if f, ok := m.files[name]; ok {
		return struct {
			io.Closer
			*bytes.Reader
		}{
			ioutil.NopCloser(nil),
			bytes.NewReader(f),
		}, nil
	}

	return nil, &os.PathError{
		Op:   "open",
		Path: name,
		Err:  os.ErrNotExist,
	}
}

func (m *mockFs) Exists(name string) (bool, bool, error) {
	name = filepath.Clean(name)
	name = m.followSymlinks(name)
	if _, ok := m.files[name]; ok {
		return ok, false, nil
	}
	if _, ok := m.dirs[name]; ok {
		return ok, true, nil
	}
	return false, false, nil
}

func (m *mockFs) IsDir(name string) (bool, error) {
	dir := filepath.Dir(name)
	if dir != "." && dir != "/" {
		isDir, err := m.IsDir(dir)

		if serr, ok := err.(*os.SyscallError); ok && serr.Err == syscall.ENOTDIR {
			isDir = false
		} else if err != nil {
			return false, err
		}

		if !isDir {
			return false, os.NewSyscallError("stat "+name, syscall.ENOTDIR)
		}
	}

	name = filepath.Clean(name)
	name = m.followSymlinks(name)

	if _, ok := m.dirs[name]; ok {
		return true, nil
	}
	if _, ok := m.files[name]; ok {
		return false, nil
	}
	return false, os.ErrNotExist
}

func (m *mockFs) IsSymlink(name string) (bool, error) {
	dir, file := saneSplit(name)
	dir = m.followSymlinks(dir)
	name = filepath.Join(dir, file)

	if _, isSymlink := m.symlinks[name]; isSymlink {
		return true, nil
	}
	if _, isDir := m.dirs[name]; isDir {
		return false, nil
	}
	if _, isFile := m.files[name]; isFile {
		return false, nil
	}
	return false, os.ErrNotExist
}

func (m *mockFs) Glob(pattern string, excludes []string, follow ShouldFollowSymlinks) (matches, dirs []string, err error) {
	return startGlob(m, pattern, excludes, follow)
}

func unescapeGlob(s string) string {
	i := 0
	for i < len(s) {
		if s[i] == '\\' {
			s = s[:i] + s[i+1:]
		} else {
			i++
		}
	}
	return s
}

func (m *mockFs) glob(pattern string) ([]string, error) {
	dir, file := saneSplit(pattern)

	dir = unescapeGlob(dir)
	toDir := m.followSymlinks(dir)

	var matches []string
	for _, f := range m.all {
		fDir, fFile := saneSplit(f)
		if toDir == fDir {
			match, err := filepath.Match(file, fFile)
			if err != nil {
				return nil, err
			}
			if f == "." && f != pattern {
				// filepath.Glob won't return "." unless the pattern was "."
				match = false
			}
			if match {
				matches = append(matches, filepath.Join(dir, fFile))
			}
		}
	}
	return matches, nil
}

type mockStat struct {
	name string
	size int64
	mode os.FileMode
}

func (ms *mockStat) Name() string       { return ms.name }
func (ms *mockStat) IsDir() bool        { return ms.Mode().IsDir() }
func (ms *mockStat) Size() int64        { return ms.size }
func (ms *mockStat) Mode() os.FileMode  { return ms.mode }
func (ms *mockStat) ModTime() time.Time { return time.Time{} }
func (ms *mockStat) Sys() interface{}   { return nil }

func (m *mockFs) Lstat(name string) (os.FileInfo, error) {
	name = filepath.Clean(name)

	ms := mockStat{
		name: name,
		size: int64(len(m.files[name])),
	}

	if isSymlink, err := m.IsSymlink(name); err != nil {
		// IsSymlink handles ErrNotExist
		return nil, err
	} else if isSymlink {
		ms.mode = os.ModeSymlink
	} else if isDir, err := m.IsDir(name); err != nil {
		return nil, err
	} else if isDir {
		ms.mode = os.ModeDir
	} else {
		ms.mode = 0
	}

	return &ms, nil
}

func (m *mockFs) ReadDirNames(name string) ([]string, error) {
	name = filepath.Clean(name)
	name = m.followSymlinks(name)

	exists, isDir, err := m.Exists(name)
	if err != nil {
		return nil, err
	}
	if !exists {
		return nil, os.ErrNotExist
	}
	if !isDir {
		return nil, os.NewSyscallError("readdir", syscall.ENOTDIR)
	}

	var ret []string
	for _, f := range m.all {
		dir, file := saneSplit(f)
		if dir == name && len(file) > 0 && file[0] != '.' {
			ret = append(ret, file)
		}
	}
	return ret, nil
}

func (m *mockFs) ListDirsRecursive(name string, follow ShouldFollowSymlinks) ([]string, error) {
	return listDirsRecursive(m, name, follow)
}

func listDirsRecursive(fs FileSystem, name string, follow ShouldFollowSymlinks) ([]string, error) {
	name = filepath.Clean(name)

	isDir, err := fs.IsDir(name)
	if err != nil {
		return nil, err
	}

	if !isDir {
		return nil, nil
	}

	dirs := []string{name}

	subDirs, err := listDirsRecursiveRelative(fs, name, follow, 0)
	if err != nil {
		return nil, err
	}

	for _, d := range subDirs {
		dirs = append(dirs, filepath.Join(name, d))
	}

	return dirs, nil
}

func listDirsRecursiveRelative(fs FileSystem, name string, follow ShouldFollowSymlinks, depth int) ([]string, error) {
	depth++
	if depth > 255 {
		return nil, fmt.Errorf("too many symlinks")
	}
	contents, err := fs.ReadDirNames(name)
	if err != nil {
		return nil, err
	}

	var dirs []string
	for _, f := range contents {
		if f[0] == '.' {
			continue
		}
		f = filepath.Join(name, f)
		if isSymlink, _ := fs.IsSymlink(f); isSymlink && follow == DontFollowSymlinks {
			continue
		}
		if isDir, _ := fs.IsDir(f); isDir {
			dirs = append(dirs, f)
			subDirs, err := listDirsRecursiveRelative(fs, f, follow, depth)
			if err != nil {
				return nil, err
			}
			for _, s := range subDirs {
				dirs = append(dirs, filepath.Join(f, s))
			}
		}
	}

	for i, d := range dirs {
		rel, err := filepath.Rel(name, d)
		if err != nil {
			return nil, err
		}
		dirs[i] = rel
	}

	return dirs, nil
}
<commit_msg>Make pathtools.FileSystem.Open return a ReaderAtSeekerCloser<commit_after>// Copyright 2016 Google Inc. All rights reserved.
//
// 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 pathtools

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"sort"
	"strings"
	"syscall"
	"time"
)

// Based on Andrew Gerrand's "10 things you (probably) dont' know about Go"

type ShouldFollowSymlinks bool

const (
	FollowSymlinks     = ShouldFollowSymlinks(true)
	DontFollowSymlinks = ShouldFollowSymlinks(false)
)

var OsFs FileSystem = osFs{}

func MockFs(files map[string][]byte) FileSystem {
	fs := &mockFs{
		files:    make(map[string][]byte, len(files)),
		dirs:     make(map[string]bool),
		symlinks: make(map[string]string),
		all:      []string(nil),
	}

	for f, b := range files {
		if tokens := strings.SplitN(f, "->", 2); len(tokens) == 2 {
			fs.symlinks[strings.TrimSpace(tokens[0])] = strings.TrimSpace(tokens[1])
			continue
		}

		fs.files[filepath.Clean(f)] = b
		dir := filepath.Dir(f)
		for dir != "." && dir != "/" {
			fs.dirs[dir] = true
			dir = filepath.Dir(dir)
		}
		fs.dirs[dir] = true
	}

	for f := range fs.files {
		fs.all = append(fs.all, f)
	}

	for d := range fs.dirs {
		fs.all = append(fs.all, d)
	}

	for s := range fs.symlinks {
		fs.all = append(fs.all, s)
	}

	sort.Strings(fs.all)

	return fs
}

type ReaderAtSeekerCloser interface {
	io.Reader
	io.ReaderAt
	io.Seeker
	io.Closer
}

type FileSystem interface {
	// Open opens a file for reading.  Follows symlinks.
	Open(name string) (ReaderAtSeekerCloser, error)

	// Exists returns whether the file exists and whether it is a directory.  Follows symlinks.
	Exists(name string) (bool, bool, error)

	Glob(pattern string, excludes []string, follow ShouldFollowSymlinks) (matches, dirs []string, err error)
	glob(pattern string) (matches []string, err error)

	// IsDir returns true if the path points to a directory, false it it points to a file.  Follows symlinks.
	// Returns os.ErrNotExist if the path does not exist or is a symlink to a path that does not exist.
	IsDir(name string) (bool, error)

	// IsSymlink returns true if the path points to a symlink, even if that symlink points to a path that does
	// not exist.  Returns os.ErrNotExist if the path does not exist.
	IsSymlink(name string) (bool, error)

	// Lstat returns info on a file without following symlinks.
	Lstat(name string) (os.FileInfo, error)

	// ListDirsRecursive returns a list of all the directories in a path, following symlinks if requested.
	ListDirsRecursive(name string, follow ShouldFollowSymlinks) (dirs []string, err error)

	// ReadDirNames returns a list of everything in a directory.
	ReadDirNames(name string) ([]string, error)
}

// osFs implements FileSystem using the local disk.
type osFs struct{}

func (osFs) Open(name string) (ReaderAtSeekerCloser, error) { return os.Open(name) }
func (osFs) Exists(name string) (bool, bool, error) {
	stat, err := os.Stat(name)
	if err == nil {
		return true, stat.IsDir(), nil
	} else if os.IsNotExist(err) {
		return false, false, nil
	} else {
		return false, false, err
	}
}

func (osFs) IsDir(name string) (bool, error) {
	info, err := os.Stat(name)
	if err != nil {
		return false, err
	}
	return info.IsDir(), nil
}

func (osFs) IsSymlink(name string) (bool, error) {
	if info, err := os.Lstat(name); err != nil {
		return false, err
	} else {
		return info.Mode()&os.ModeSymlink != 0, nil
	}
}

func (fs osFs) Glob(pattern string, excludes []string, follow ShouldFollowSymlinks) (matches, dirs []string, err error) {
	return startGlob(fs, pattern, excludes, follow)
}

func (osFs) glob(pattern string) ([]string, error) {
	return filepath.Glob(pattern)
}

func (osFs) Lstat(path string) (stats os.FileInfo, err error) {
	return os.Lstat(path)
}

// Returns a list of all directories under dir
func (osFs) ListDirsRecursive(name string, follow ShouldFollowSymlinks) (dirs []string, err error) {
	return listDirsRecursive(OsFs, name, follow)
}

func (osFs) ReadDirNames(name string) ([]string, error) {
	dir, err := os.Open(name)
	if err != nil {
		return nil, err
	}
	defer dir.Close()

	contents, err := dir.Readdirnames(-1)
	if err != nil {
		return nil, err
	}

	sort.Strings(contents)
	return contents, nil
}

type mockFs struct {
	files    map[string][]byte
	dirs     map[string]bool
	symlinks map[string]string
	all      []string
}

func (m *mockFs) followSymlinks(name string) string {
	dir, file := saneSplit(name)
	if dir != "." && dir != "/" {
		dir = m.followSymlinks(dir)
	}
	name = filepath.Join(dir, file)

	for i := 0; i < 255; i++ {
		i++
		if i > 255 {
			panic("symlink loop")
		}
		to, exists := m.symlinks[name]
		if !exists {
			break
		}
		if filepath.IsAbs(to) {
			name = to
		} else {
			name = filepath.Join(dir, to)
		}
	}
	return name
}

func (m *mockFs) Open(name string) (ReaderAtSeekerCloser, error) {
	name = filepath.Clean(name)
	name = m.followSymlinks(name)
	if f, ok := m.files[name]; ok {
		return struct {
			io.Closer
			*bytes.Reader
		}{
			ioutil.NopCloser(nil),
			bytes.NewReader(f),
		}, nil
	}

	return nil, &os.PathError{
		Op:   "open",
		Path: name,
		Err:  os.ErrNotExist,
	}
}

func (m *mockFs) Exists(name string) (bool, bool, error) {
	name = filepath.Clean(name)
	name = m.followSymlinks(name)
	if _, ok := m.files[name]; ok {
		return ok, false, nil
	}
	if _, ok := m.dirs[name]; ok {
		return ok, true, nil
	}
	return false, false, nil
}

func (m *mockFs) IsDir(name string) (bool, error) {
	dir := filepath.Dir(name)
	if dir != "." && dir != "/" {
		isDir, err := m.IsDir(dir)

		if serr, ok := err.(*os.SyscallError); ok && serr.Err == syscall.ENOTDIR {
			isDir = false
		} else if err != nil {
			return false, err
		}

		if !isDir {
			return false, os.NewSyscallError("stat "+name, syscall.ENOTDIR)
		}
	}

	name = filepath.Clean(name)
	name = m.followSymlinks(name)

	if _, ok := m.dirs[name]; ok {
		return true, nil
	}
	if _, ok := m.files[name]; ok {
		return false, nil
	}
	return false, os.ErrNotExist
}

func (m *mockFs) IsSymlink(name string) (bool, error) {
	dir, file := saneSplit(name)
	dir = m.followSymlinks(dir)
	name = filepath.Join(dir, file)

	if _, isSymlink := m.symlinks[name]; isSymlink {
		return true, nil
	}
	if _, isDir := m.dirs[name]; isDir {
		return false, nil
	}
	if _, isFile := m.files[name]; isFile {
		return false, nil
	}
	return false, os.ErrNotExist
}

func (m *mockFs) Glob(pattern string, excludes []string, follow ShouldFollowSymlinks) (matches, dirs []string, err error) {
	return startGlob(m, pattern, excludes, follow)
}

func unescapeGlob(s string) string {
	i := 0
	for i < len(s) {
		if s[i] == '\\' {
			s = s[:i] + s[i+1:]
		} else {
			i++
		}
	}
	return s
}

func (m *mockFs) glob(pattern string) ([]string, error) {
	dir, file := saneSplit(pattern)

	dir = unescapeGlob(dir)
	toDir := m.followSymlinks(dir)

	var matches []string
	for _, f := range m.all {
		fDir, fFile := saneSplit(f)
		if toDir == fDir {
			match, err := filepath.Match(file, fFile)
			if err != nil {
				return nil, err
			}
			if f == "." && f != pattern {
				// filepath.Glob won't return "." unless the pattern was "."
				match = false
			}
			if match {
				matches = append(matches, filepath.Join(dir, fFile))
			}
		}
	}
	return matches, nil
}

type mockStat struct {
	name string
	size int64
	mode os.FileMode
}

func (ms *mockStat) Name() string       { return ms.name }
func (ms *mockStat) IsDir() bool        { return ms.Mode().IsDir() }
func (ms *mockStat) Size() int64        { return ms.size }
func (ms *mockStat) Mode() os.FileMode  { return ms.mode }
func (ms *mockStat) ModTime() time.Time { return time.Time{} }
func (ms *mockStat) Sys() interface{}   { return nil }

func (m *mockFs) Lstat(name string) (os.FileInfo, error) {
	name = filepath.Clean(name)

	ms := mockStat{
		name: name,
		size: int64(len(m.files[name])),
	}

	if isSymlink, err := m.IsSymlink(name); err != nil {
		// IsSymlink handles ErrNotExist
		return nil, err
	} else if isSymlink {
		ms.mode = os.ModeSymlink
	} else if isDir, err := m.IsDir(name); err != nil {
		return nil, err
	} else if isDir {
		ms.mode = os.ModeDir
	} else {
		ms.mode = 0
	}

	return &ms, nil
}

func (m *mockFs) ReadDirNames(name string) ([]string, error) {
	name = filepath.Clean(name)
	name = m.followSymlinks(name)

	exists, isDir, err := m.Exists(name)
	if err != nil {
		return nil, err
	}
	if !exists {
		return nil, os.ErrNotExist
	}
	if !isDir {
		return nil, os.NewSyscallError("readdir", syscall.ENOTDIR)
	}

	var ret []string
	for _, f := range m.all {
		dir, file := saneSplit(f)
		if dir == name && len(file) > 0 && file[0] != '.' {
			ret = append(ret, file)
		}
	}
	return ret, nil
}

func (m *mockFs) ListDirsRecursive(name string, follow ShouldFollowSymlinks) ([]string, error) {
	return listDirsRecursive(m, name, follow)
}

func listDirsRecursive(fs FileSystem, name string, follow ShouldFollowSymlinks) ([]string, error) {
	name = filepath.Clean(name)

	isDir, err := fs.IsDir(name)
	if err != nil {
		return nil, err
	}

	if !isDir {
		return nil, nil
	}

	dirs := []string{name}

	subDirs, err := listDirsRecursiveRelative(fs, name, follow, 0)
	if err != nil {
		return nil, err
	}

	for _, d := range subDirs {
		dirs = append(dirs, filepath.Join(name, d))
	}

	return dirs, nil
}

func listDirsRecursiveRelative(fs FileSystem, name string, follow ShouldFollowSymlinks, depth int) ([]string, error) {
	depth++
	if depth > 255 {
		return nil, fmt.Errorf("too many symlinks")
	}
	contents, err := fs.ReadDirNames(name)
	if err != nil {
		return nil, err
	}

	var dirs []string
	for _, f := range contents {
		if f[0] == '.' {
			continue
		}
		f = filepath.Join(name, f)
		if isSymlink, _ := fs.IsSymlink(f); isSymlink && follow == DontFollowSymlinks {
			continue
		}
		if isDir, _ := fs.IsDir(f); isDir {
			dirs = append(dirs, f)
			subDirs, err := listDirsRecursiveRelative(fs, f, follow, depth)
			if err != nil {
				return nil, err
			}
			for _, s := range subDirs {
				dirs = append(dirs, filepath.Join(f, s))
			}
		}
	}

	for i, d := range dirs {
		rel, err := filepath.Rel(name, d)
		if err != nil {
			return nil, err
		}
		dirs[i] = rel
	}

	return dirs, nil
}
<|endoftext|>


<commit_before>package orm

import (
	"strings"

	"github.com/lfq7413/tomato/utils"
	"gopkg.in/mgo.v2/bson"
)

var adapter *MongoAdapter
var schemaPromise *Schema

func init() {
	adapter = &MongoAdapter{
		collectionList: []string{},
	}
}

// AdaptiveCollection ...
func AdaptiveCollection(className string) *MongoCollection {
	return adapter.adaptiveCollection(className)
}

// SchemaCollection 获取 Schema 表
func SchemaCollection() *MongoSchemaCollection {
	return adapter.schemaCollection()
}

// CollectionExists ...
func CollectionExists(className string) bool {
	return adapter.collectionExists(className)
}

// DropCollection ...
func DropCollection(className string) error {
	return adapter.dropCollection(className)
}

// Find ...
func Find(className string, where, options map[string]interface{}) []interface{} {
	// TODO 处理错误
	if options == nil {
		options = bson.M{}
	}
	if where == nil {
		where = bson.M{}
	}

	mongoOptions := bson.M{}
	if options["skip"] != nil {
		mongoOptions["skip"] = options["skip"]
	}
	if options["limit"] != nil {
		mongoOptions["limit"] = options["limit"]
	}

	var isMaster bool
	if _, ok := options["acl"]; ok {
		isMaster = false
	} else {
		isMaster = true
	}
	var aclGroup []string
	if options["acl"] == nil {
		aclGroup = []string{}
	} else {
		aclGroup = options["acl"].([]string)
	}

	acceptor := func(schema *Schema) bool {
		return schema.hasKeys(className, keysForQuery(where))
	}
	schema := LoadSchema(acceptor)

	if options["sort"] != nil {
		sortKeys := []string{}
		keys := options["sort"].([]string)
		for _, key := range keys {
			mongoKey := ""
			if strings.HasPrefix(key, "-") {
				mongoKey = "-" + transformKey(schema, className, key[1:])
			} else {
				mongoKey = transformKey(schema, className, key)
			}
			sortKeys = append(sortKeys, mongoKey)
		}
		mongoOptions["sort"] = sortKeys
	}

	if isMaster == false {
		op := "find"
		if len(where) == 1 && where["objectId"] != nil && utils.String(where["objectId"]) != "" {
			op = "get"
		}
		schema.validatePermission(className, aclGroup, op)
	}

	reduceRelationKeys(className, where)
	reduceInRelation(className, where, schema)

	coll := AdaptiveCollection(className)
	mongoWhere := transformWhere(schema, className, where)
	// 组装查询条件，查找可被当前用户修改的对象
	if options["acl"] != nil {
		queryPerms := []interface{}{}
		perm := bson.M{
			"_rperm": bson.M{"$exists": false},
		}
		queryPerms = append(queryPerms, perm)
		perm = bson.M{
			"_rperm": bson.M{"$in": []string{"*"}},
		}
		queryPerms = append(queryPerms, perm)
		for _, acl := range aclGroup {
			perm = bson.M{
				"_rperm": bson.M{"$in": []string{acl}},
			}
			queryPerms = append(queryPerms, perm)
		}

		mongoWhere = bson.M{
			"$and": []interface{}{
				mongoWhere,
				bson.M{"$or": queryPerms},
			},
		}
	}

	if options["count"] != nil {
		delete(mongoOptions, "limit")
		count := coll.Count(mongoWhere, mongoOptions)
		return []interface{}{count}
	}

	mongoResults := coll.find(mongoWhere, mongoOptions)
	results := []interface{}{}
	for _, r := range mongoResults {
		result := untransformObject(schema, isMaster, aclGroup, className, r)
		results = append(results, result)
	}
	return results

}

// Destroy ...
func Destroy(className string, where map[string]interface{}, options map[string]interface{}) {
	// TODO 处理错误
	var isMaster bool
	if _, ok := options["acl"]; ok {
		isMaster = false
	} else {
		isMaster = true
	}
	var aclGroup []string
	if options["acl"] == nil {
		aclGroup = []string{}
	} else {
		aclGroup = options["acl"].([]string)
	}

	schema := LoadSchema(nil)
	if isMaster == false {
		schema.validatePermission(className, aclGroup, "delete")
	}

	coll := AdaptiveCollection(className)
	mongoWhere := transformWhere(schema, className, where)
	// 组装查询条件，查找可被当前用户修改的对象
	if options["acl"] != nil {
		writePerms := []interface{}{}
		perm := bson.M{
			"_wperm": bson.M{"$exists": false},
		}
		writePerms = append(writePerms, perm)
		for _, acl := range aclGroup {
			perm = bson.M{
				"_wperm": bson.M{"$in": []string{acl}},
			}
			writePerms = append(writePerms, perm)
		}

		mongoWhere = bson.M{
			"$and": []interface{}{
				mongoWhere,
				bson.M{"$or": writePerms},
			},
		}
	}
	coll.deleteMany(mongoWhere)
	// TODO 处理返回错误
}

// Update ...
func Update(className string, where, data, options map[string]interface{}) (bson.M, error) {
	// TODO 处理错误
	data = utils.CopyMap(data)
	acceptor := func(schema *Schema) bool {
		keys := []string{}
		for k := range where {
			keys = append(keys, k)
		}
		return schema.hasKeys(className, keys)
	}
	var isMaster bool
	if _, ok := options["acl"]; ok {
		isMaster = false
	} else {
		isMaster = true
	}
	var aclGroup []string
	if options["acl"] == nil {
		aclGroup = []string{}
	} else {
		aclGroup = options["acl"].([]string)
	}

	schema := LoadSchema(acceptor)
	if isMaster == false {
		schema.validatePermission(className, aclGroup, "update")
	}
	handleRelationUpdates(className, utils.String(where["objectId"]), data)

	coll := AdaptiveCollection(className)
	mongoWhere := transformWhere(schema, className, where)
	// 组装查询条件，查找可被当前用户修改的对象
	if options["acl"] != nil {
		writePerms := []interface{}{}
		perm := bson.M{
			"_wperm": bson.M{"$exists": false},
		}
		writePerms = append(writePerms, perm)
		for _, acl := range aclGroup {
			perm = bson.M{
				"_wperm": bson.M{"$in": []string{acl}},
			}
			writePerms = append(writePerms, perm)
		}

		mongoWhere = bson.M{
			"$and": []interface{}{
				mongoWhere,
				bson.M{"$or": writePerms},
			},
		}
	}
	mongoUpdate := transformUpdate(schema, className, data)

	result := coll.findOneAndUpdate(mongoWhere, mongoUpdate)
	// TODO 处理返回错误

	response := bson.M{}
	if mongoUpdate["$inc"] != nil && utils.MapInterface(mongoUpdate["$inc"]) != nil {
		inc := utils.MapInterface(mongoUpdate["$inc"])
		for k := range inc {
			response[k] = result[k]
		}
	}

	return response, nil
}

// Create ...
func Create(className string, data, options map[string]interface{}) error {
	// TODO 处理错误
	data = utils.CopyMap(data)
	var isMaster bool
	if _, ok := options["acl"]; ok {
		isMaster = false
	} else {
		isMaster = true
	}
	var aclGroup []string
	if options["acl"] == nil {
		aclGroup = []string{}
	} else {
		aclGroup = options["acl"].([]string)
	}

	validateClassName(className)

	schema := LoadSchema(nil)
	if isMaster == false {
		schema.validatePermission(className, aclGroup, "create")
	}

	handleRelationUpdates(className, "", data)

	coll := AdaptiveCollection(className)
	mongoObject := transformCreate(schema, className, data)
	coll.insertOne(mongoObject)

	return nil
}

func validateClassName(className string) {
	// TODO 处理错误
	if ClassNameIsValid(className) == false {
		// TODO 无效类名
		return
	}
}

func handleRelationUpdates(className, objectID string, update map[string]interface{}) {
	// TODO 处理错误
	objID := objectID
	if utils.String(update["objectId"]) != "" {
		objID = utils.String(update["objectId"])
	}

	var process func(op interface{}, key string)
	process = func(op interface{}, key string) {
		if op == nil || utils.MapInterface(op) == nil || utils.MapInterface(op)["__op"] == nil {
			return
		}
		opMap := utils.MapInterface(op)
		p := utils.String(opMap["__op"])
		if p == "AddRelation" {
			delete(update, key)
			objects := utils.SliceInterface(opMap["objects"])
			for _, object := range objects {
				relationID := utils.String(utils.MapInterface(object)["objectId"])
				addRelation(key, className, objID, relationID)
			}
		} else if p == "RemoveRelation" {
			delete(update, key)
			objects := utils.SliceInterface(opMap["objects"])
			for _, object := range objects {
				relationID := utils.String(utils.MapInterface(object)["objectId"])
				removeRelation(key, className, objID, relationID)
			}
		} else if p == "Batch" {
			ops := utils.SliceInterface(opMap["ops"])
			for _, x := range ops {
				process(x, key)
			}
		}
	}

	for k, v := range update {
		process(v, k)
	}

}

func addRelation(key, fromClassName, fromID, toID string) {
	// TODO 处理错误
	doc := map[string]interface{}{
		"relatedId": toID,
		"owningId":  fromID,
	}
	className := "_Join:" + key + ":" + fromClassName
	coll := AdaptiveCollection(className)
	coll.upsertOne(doc, doc)
}

func removeRelation(key, fromClassName, fromID, toID string) {
	// TODO 处理错误
	doc := map[string]interface{}{
		"relatedId": toID,
		"owningId":  fromID,
	}
	className := "_Join:" + key + ":" + fromClassName
	coll := AdaptiveCollection(className)
	coll.deleteOne(doc)
}

// ValidateObject ...
func ValidateObject(className string, object, where, options map[string]interface{}) error {
	// TODO 处理错误
	schema := LoadSchema(nil)
	acl := []string{}
	if options["acl"] != nil {
		if v, ok := options["acl"].([]string); ok {
			acl = v
		}
	}

	canAddField(schema, className, object, acl)

	schema.validateObject(className, object, where)

	return nil
}

// LoadSchema 加载 Schema
func LoadSchema(acceptor func(*Schema) bool) *Schema {
	if schemaPromise == nil {
		collection := SchemaCollection()
		schemaPromise = Load(collection)
		return schemaPromise
	}

	if acceptor == nil {
		return schemaPromise
	}
	if acceptor(schemaPromise) {
		return schemaPromise
	}

	collection := SchemaCollection()
	schemaPromise = Load(collection)
	return schemaPromise
}

// canAddField ...
func canAddField(schema *Schema, className string, object map[string]interface{}, acl []string) {
	// TODO 处理错误
	if schema.data[className] == nil {
		return
	}
	classSchema := utils.MapInterface(schema.data[className])

	schemaFields := []string{}
	for k := range classSchema {
		schemaFields = append(schemaFields, k)
	}
	// 收集新增的字段
	newKeys := []string{}
	for k := range object {
		t := true
		for _, v := range schemaFields {
			if k == v {
				t = false
				break
			}
		}
		if t {
			newKeys = append(newKeys, k)
		}
	}

	if len(newKeys) > 0 {
		schema.validatePermission(className, acl, "addField")
	}
}

func keysForQuery(query bson.M) []string {
	answer := []string{}

	var s interface{}
	if query["$and"] != nil {
		s = query["$and"]
	} else {
		s = query["$or"]
	}

	if s != nil {
		sublist := utils.SliceInterface(s)
		for _, v := range sublist {
			subquery := utils.MapInterface(v)
			answer = append(answer, keysForQuery(subquery)...)
		}
		return answer
	}

	for k := range query {
		answer = append(answer, k)
	}

	return answer
}

func reduceRelationKeys(className string, query bson.M) {
	// TODO
}

func reduceInRelation(className string, query bson.M, schema *Schema) {
	// TODO
}

func untransformObject(schema *Schema, isMaster bool, aclGroup []string, className string, mongoObject bson.M) bson.M {
	// TODO
	object := untransformObjectT(schema, className, mongoObject, false)
	if className != "_User" {
		return utils.MapInterface(object)
	}

	if isMaster {
		return utils.MapInterface(object)
	}

	return nil
}
<commit_msg>完成 untransformObject<commit_after>package orm

import (
	"strings"

	"github.com/lfq7413/tomato/utils"
	"gopkg.in/mgo.v2/bson"
)

var adapter *MongoAdapter
var schemaPromise *Schema

func init() {
	adapter = &MongoAdapter{
		collectionList: []string{},
	}
}

// AdaptiveCollection ...
func AdaptiveCollection(className string) *MongoCollection {
	return adapter.adaptiveCollection(className)
}

// SchemaCollection 获取 Schema 表
func SchemaCollection() *MongoSchemaCollection {
	return adapter.schemaCollection()
}

// CollectionExists ...
func CollectionExists(className string) bool {
	return adapter.collectionExists(className)
}

// DropCollection ...
func DropCollection(className string) error {
	return adapter.dropCollection(className)
}

// Find ...
func Find(className string, where, options map[string]interface{}) []interface{} {
	// TODO 处理错误
	if options == nil {
		options = bson.M{}
	}
	if where == nil {
		where = bson.M{}
	}

	mongoOptions := bson.M{}
	if options["skip"] != nil {
		mongoOptions["skip"] = options["skip"]
	}
	if options["limit"] != nil {
		mongoOptions["limit"] = options["limit"]
	}

	var isMaster bool
	if _, ok := options["acl"]; ok {
		isMaster = false
	} else {
		isMaster = true
	}
	var aclGroup []string
	if options["acl"] == nil {
		aclGroup = []string{}
	} else {
		aclGroup = options["acl"].([]string)
	}

	acceptor := func(schema *Schema) bool {
		return schema.hasKeys(className, keysForQuery(where))
	}
	schema := LoadSchema(acceptor)

	if options["sort"] != nil {
		sortKeys := []string{}
		keys := options["sort"].([]string)
		for _, key := range keys {
			mongoKey := ""
			if strings.HasPrefix(key, "-") {
				mongoKey = "-" + transformKey(schema, className, key[1:])
			} else {
				mongoKey = transformKey(schema, className, key)
			}
			sortKeys = append(sortKeys, mongoKey)
		}
		mongoOptions["sort"] = sortKeys
	}

	if isMaster == false {
		op := "find"
		if len(where) == 1 && where["objectId"] != nil && utils.String(where["objectId"]) != "" {
			op = "get"
		}
		schema.validatePermission(className, aclGroup, op)
	}

	reduceRelationKeys(className, where)
	reduceInRelation(className, where, schema)

	coll := AdaptiveCollection(className)
	mongoWhere := transformWhere(schema, className, where)
	// 组装查询条件，查找可被当前用户修改的对象
	if options["acl"] != nil {
		queryPerms := []interface{}{}
		perm := bson.M{
			"_rperm": bson.M{"$exists": false},
		}
		queryPerms = append(queryPerms, perm)
		perm = bson.M{
			"_rperm": bson.M{"$in": []string{"*"}},
		}
		queryPerms = append(queryPerms, perm)
		for _, acl := range aclGroup {
			perm = bson.M{
				"_rperm": bson.M{"$in": []string{acl}},
			}
			queryPerms = append(queryPerms, perm)
		}

		mongoWhere = bson.M{
			"$and": []interface{}{
				mongoWhere,
				bson.M{"$or": queryPerms},
			},
		}
	}

	if options["count"] != nil {
		delete(mongoOptions, "limit")
		count := coll.Count(mongoWhere, mongoOptions)
		return []interface{}{count}
	}

	mongoResults := coll.find(mongoWhere, mongoOptions)
	results := []interface{}{}
	for _, r := range mongoResults {
		result := untransformObject(schema, isMaster, aclGroup, className, r)
		results = append(results, result)
	}
	return results

}

// Destroy ...
func Destroy(className string, where map[string]interface{}, options map[string]interface{}) {
	// TODO 处理错误
	var isMaster bool
	if _, ok := options["acl"]; ok {
		isMaster = false
	} else {
		isMaster = true
	}
	var aclGroup []string
	if options["acl"] == nil {
		aclGroup = []string{}
	} else {
		aclGroup = options["acl"].([]string)
	}

	schema := LoadSchema(nil)
	if isMaster == false {
		schema.validatePermission(className, aclGroup, "delete")
	}

	coll := AdaptiveCollection(className)
	mongoWhere := transformWhere(schema, className, where)
	// 组装查询条件，查找可被当前用户修改的对象
	if options["acl"] != nil {
		writePerms := []interface{}{}
		perm := bson.M{
			"_wperm": bson.M{"$exists": false},
		}
		writePerms = append(writePerms, perm)
		for _, acl := range aclGroup {
			perm = bson.M{
				"_wperm": bson.M{"$in": []string{acl}},
			}
			writePerms = append(writePerms, perm)
		}

		mongoWhere = bson.M{
			"$and": []interface{}{
				mongoWhere,
				bson.M{"$or": writePerms},
			},
		}
	}
	coll.deleteMany(mongoWhere)
	// TODO 处理返回错误
}

// Update ...
func Update(className string, where, data, options map[string]interface{}) (bson.M, error) {
	// TODO 处理错误
	data = utils.CopyMap(data)
	acceptor := func(schema *Schema) bool {
		keys := []string{}
		for k := range where {
			keys = append(keys, k)
		}
		return schema.hasKeys(className, keys)
	}
	var isMaster bool
	if _, ok := options["acl"]; ok {
		isMaster = false
	} else {
		isMaster = true
	}
	var aclGroup []string
	if options["acl"] == nil {
		aclGroup = []string{}
	} else {
		aclGroup = options["acl"].([]string)
	}

	schema := LoadSchema(acceptor)
	if isMaster == false {
		schema.validatePermission(className, aclGroup, "update")
	}
	handleRelationUpdates(className, utils.String(where["objectId"]), data)

	coll := AdaptiveCollection(className)
	mongoWhere := transformWhere(schema, className, where)
	// 组装查询条件，查找可被当前用户修改的对象
	if options["acl"] != nil {
		writePerms := []interface{}{}
		perm := bson.M{
			"_wperm": bson.M{"$exists": false},
		}
		writePerms = append(writePerms, perm)
		for _, acl := range aclGroup {
			perm = bson.M{
				"_wperm": bson.M{"$in": []string{acl}},
			}
			writePerms = append(writePerms, perm)
		}

		mongoWhere = bson.M{
			"$and": []interface{}{
				mongoWhere,
				bson.M{"$or": writePerms},
			},
		}
	}
	mongoUpdate := transformUpdate(schema, className, data)

	result := coll.findOneAndUpdate(mongoWhere, mongoUpdate)
	// TODO 处理返回错误

	response := bson.M{}
	if mongoUpdate["$inc"] != nil && utils.MapInterface(mongoUpdate["$inc"]) != nil {
		inc := utils.MapInterface(mongoUpdate["$inc"])
		for k := range inc {
			response[k] = result[k]
		}
	}

	return response, nil
}

// Create ...
func Create(className string, data, options map[string]interface{}) error {
	// TODO 处理错误
	data = utils.CopyMap(data)
	var isMaster bool
	if _, ok := options["acl"]; ok {
		isMaster = false
	} else {
		isMaster = true
	}
	var aclGroup []string
	if options["acl"] == nil {
		aclGroup = []string{}
	} else {
		aclGroup = options["acl"].([]string)
	}

	validateClassName(className)

	schema := LoadSchema(nil)
	if isMaster == false {
		schema.validatePermission(className, aclGroup, "create")
	}

	handleRelationUpdates(className, "", data)

	coll := AdaptiveCollection(className)
	mongoObject := transformCreate(schema, className, data)
	coll.insertOne(mongoObject)

	return nil
}

func validateClassName(className string) {
	// TODO 处理错误
	if ClassNameIsValid(className) == false {
		// TODO 无效类名
		return
	}
}

func handleRelationUpdates(className, objectID string, update map[string]interface{}) {
	// TODO 处理错误
	objID := objectID
	if utils.String(update["objectId"]) != "" {
		objID = utils.String(update["objectId"])
	}

	var process func(op interface{}, key string)
	process = func(op interface{}, key string) {
		if op == nil || utils.MapInterface(op) == nil || utils.MapInterface(op)["__op"] == nil {
			return
		}
		opMap := utils.MapInterface(op)
		p := utils.String(opMap["__op"])
		if p == "AddRelation" {
			delete(update, key)
			objects := utils.SliceInterface(opMap["objects"])
			for _, object := range objects {
				relationID := utils.String(utils.MapInterface(object)["objectId"])
				addRelation(key, className, objID, relationID)
			}
		} else if p == "RemoveRelation" {
			delete(update, key)
			objects := utils.SliceInterface(opMap["objects"])
			for _, object := range objects {
				relationID := utils.String(utils.MapInterface(object)["objectId"])
				removeRelation(key, className, objID, relationID)
			}
		} else if p == "Batch" {
			ops := utils.SliceInterface(opMap["ops"])
			for _, x := range ops {
				process(x, key)
			}
		}
	}

	for k, v := range update {
		process(v, k)
	}

}

func addRelation(key, fromClassName, fromID, toID string) {
	// TODO 处理错误
	doc := map[string]interface{}{
		"relatedId": toID,
		"owningId":  fromID,
	}
	className := "_Join:" + key + ":" + fromClassName
	coll := AdaptiveCollection(className)
	coll.upsertOne(doc, doc)
}

func removeRelation(key, fromClassName, fromID, toID string) {
	// TODO 处理错误
	doc := map[string]interface{}{
		"relatedId": toID,
		"owningId":  fromID,
	}
	className := "_Join:" + key + ":" + fromClassName
	coll := AdaptiveCollection(className)
	coll.deleteOne(doc)
}

// ValidateObject ...
func ValidateObject(className string, object, where, options map[string]interface{}) error {
	// TODO 处理错误
	schema := LoadSchema(nil)
	acl := []string{}
	if options["acl"] != nil {
		if v, ok := options["acl"].([]string); ok {
			acl = v
		}
	}

	canAddField(schema, className, object, acl)

	schema.validateObject(className, object, where)

	return nil
}

// LoadSchema 加载 Schema
func LoadSchema(acceptor func(*Schema) bool) *Schema {
	if schemaPromise == nil {
		collection := SchemaCollection()
		schemaPromise = Load(collection)
		return schemaPromise
	}

	if acceptor == nil {
		return schemaPromise
	}
	if acceptor(schemaPromise) {
		return schemaPromise
	}

	collection := SchemaCollection()
	schemaPromise = Load(collection)
	return schemaPromise
}

// canAddField ...
func canAddField(schema *Schema, className string, object map[string]interface{}, acl []string) {
	// TODO 处理错误
	if schema.data[className] == nil {
		return
	}
	classSchema := utils.MapInterface(schema.data[className])

	schemaFields := []string{}
	for k := range classSchema {
		schemaFields = append(schemaFields, k)
	}
	// 收集新增的字段
	newKeys := []string{}
	for k := range object {
		t := true
		for _, v := range schemaFields {
			if k == v {
				t = false
				break
			}
		}
		if t {
			newKeys = append(newKeys, k)
		}
	}

	if len(newKeys) > 0 {
		schema.validatePermission(className, acl, "addField")
	}
}

func keysForQuery(query bson.M) []string {
	answer := []string{}

	var s interface{}
	if query["$and"] != nil {
		s = query["$and"]
	} else {
		s = query["$or"]
	}

	if s != nil {
		sublist := utils.SliceInterface(s)
		for _, v := range sublist {
			subquery := utils.MapInterface(v)
			answer = append(answer, keysForQuery(subquery)...)
		}
		return answer
	}

	for k := range query {
		answer = append(answer, k)
	}

	return answer
}

func reduceRelationKeys(className string, query bson.M) {
	// TODO
}

func reduceInRelation(className string, query bson.M, schema *Schema) {
	// TODO
}

func untransformObject(schema *Schema, isMaster bool, aclGroup []string, className string, mongoObject bson.M) bson.M {
	// TODO
	res := untransformObjectT(schema, className, mongoObject, false)
	object := utils.MapInterface(res)
	if className != "_User" {
		return object
	}
	// 以下单独处理 _User 类
	if isMaster {
		return object
	}
	// 当前用户返回所有信息
	id := utils.String(object["objectId"])
	for _, v := range aclGroup {
		if v == id {
			return object
		}
	}
	// 其他用户删除相关信息
	delete(object, "authData")
	delete(object, "sessionToken")
	return object
}
<|endoftext|>


<commit_before>// Copyright 2015,2016,2017,2018,2019 SeukWon Kang (kasworld@gmail.com)
// 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.

// 2d tile space direction
package direction

import (
	"math"

	"github.com/kasworld/go-abs"
)

type Direction_Type uint8

//go:generate stringer -type=Direction_Type
const (
	Dir_stop Direction_Type = iota
	Dir_n
	Dir_ne
	Dir_e
	Dir_se
	Dir_s
	Dir_sw
	Dir_w
	Dir_nw
	Direction_Count int = iota
)

func (i Direction_Type) String() string {
	return "Dir_" + Dir2Info[i].Name
}

var Dir2Info = []struct {
	Name string
	Vt   [2]int
	Len  float64
}{
	Dir_stop: {"Stop", [2]int{0, 0}, 0.0},
	Dir_n:    {"N", [2]int{0, -1}, 1.0},
	Dir_ne:   {"NE", [2]int{1, -1}, math.Sqrt2},
	Dir_e:    {"E", [2]int{1, 0}, 1.0},
	Dir_se:   {"SE", [2]int{1, 1}, math.Sqrt2},
	Dir_s:    {"S", [2]int{0, 1}, 1.0},
	Dir_sw:   {"SW", [2]int{-1, 1}, math.Sqrt2},
	Dir_w:    {"W", [2]int{-1, 0}, 1.0},
	Dir_nw:   {"NW", [2]int{-1, -1}, math.Sqrt2},
}

var vt2Dir = [3][3]Direction_Type{}

func init() {
	// println("init direction")
	for i, v := range Dir2Info {
		vt2Dir[v.Vt[0]+1][v.Vt[1]+1] = Direction_Type(i)
	}
}

func (dt Direction_Type) Name() string {
	return Dir2Info[dt].Name
}

func (dt Direction_Type) Vector() [2]int {
	return Dir2Info[dt].Vt
}

func (dt Direction_Type) DxDy() (int, int) {
	return Dir2Info[dt].Vt[0], Dir2Info[dt].Vt[1]
}

func (dt Direction_Type) Dx() int {
	return Dir2Info[dt].Vt[0]
}
func (dt Direction_Type) Dy() int {
	return Dir2Info[dt].Vt[1]
}

func (dt Direction_Type) Len() float64 {
	return Dir2Info[dt].Len
}

func (dt Direction_Type) TurnDir(turn int8) Direction_Type {
	if dt == Dir_stop {
		return dt
	}
	turn %= 8
	return Direction_Type((int8(dt)-1+turn+8)%8 + 1)
}

func (dt Direction_Type) ReverseDir() Direction_Type {
	vt := Dir2Info[dt].Vt
	return Vt2Dir(-vt[0], -vt[1])
}
func (dt Direction_Type) InverseX() Direction_Type {
	vt := Dir2Info[dt].Vt
	return Vt2Dir(-vt[0], vt[1])
}
func (dt Direction_Type) InverseY() Direction_Type {
	vt := Dir2Info[dt].Vt
	return Vt2Dir(vt[0], -vt[1])
}

func (dt Direction_Type) IsValid() bool {
	return dt >= Dir_stop && dt <= Dir_nw
}

func (dt Direction_Type) Add(dt2 Direction_Type) Direction_Type {
	dx := dt.Dx() + dt2.Dx()
	dy := dt.Dy() + dt2.Dy()
	dx = abs.Signi(dx)
	dy = abs.Signi(dy)
	return Vt2Dir(dx, dy)
}

func (dt Direction_Type) MulXY(x, y int) (int, int) {
	return Dir2Info[dt].Vt[0] * x, Dir2Info[dt].Vt[1] * y
}

////

func Vt2Dir(x, y int) Direction_Type { // -1 ~ 1
	return vt2Dir[x+1][y+1]
}
func VtValidate(x, y int) bool {
	return x >= -1 && x <= 1 && y >= -1 && y <= 1
}

// func RandDir(rndfn func(st, ed int) int) Direction_Type {
// 	return Direction_Type(rndfn(1, 9))
// }

// find remote pos direction 8way
func DxDy2Dir8(dx, dy int) Direction_Type {
	dxs, dxv := abs.SignAbsi(dx)
	dys, dyv := abs.SignAbsi(dy)
	if dxv > dyv*2 {
		dys = 0
	}
	if dyv > dxv*2 {
		dxs = 0
	}
	return Vt2Dir(dxs, dys)
}

// find remote pos direction 4way
func DxDy2Dir4(dx, dy int) Direction_Type {
	dxs, dxv := abs.SignAbsi(dx)
	dys, dyv := abs.SignAbsi(dy)
	if dxv >= dyv {
		dys = 0
	} else {
		dxs = 0
	}
	return Vt2Dir(dxs, dys)
}

// contact only
func DxDy2Dir(dx, dy int) Direction_Type {
	dxs, _ := abs.SignAbsi(dx)
	dys, _ := abs.SignAbsi(dy)
	return Vt2Dir(dxs, dys)
}

// wrap dxdy
func CalcDxDyWrapped(dx, dy, xLen, yLen int) (int, int) {
	xs, xa := abs.SignAbsi(dx)
	if xa > xLen/2 {
		dx = xs * (xa - xLen)
	}
	ys, ya := abs.SignAbsi(dy)
	if ya > yLen/2 {
		dy = ys * (ya - yLen)
	}
	return dx, dy
}

// iscontact and dir with wrap
func CalcContactDirWrapped(from, to [2]int, xLen, yLen int) (bool, Direction_Type) {
	dx, dy := CalcDxDyWrapped(to[0]-from[0], to[1]-from[1], xLen, yLen)
	contact := VtValidate(dx, dy)
	if !contact {
		return false, 0
	}
	return true, Vt2Dir(dx, dy)
}

func CalcContactDirWrappedXY(x1, y1, x2, y2, xLen, yLen int) (bool, Direction_Type) {
	dx, dy := CalcDxDyWrapped(x2-x1, y2-y1, xLen, yLen)
	contact := VtValidate(dx, dy)
	if !contact {
		return false, 0
	}
	return true, Vt2Dir(dx, dy)
}

func Call8WayTile(ox, oy int, fn func(int, int) bool) []Direction_Type {
	TileDirs := []Direction_Type{}
	for i := Direction_Type(1); i <= 8; i++ {
		x, y := ox+i.Vector()[0], oy+i.Vector()[1]
		if fn(x, y) {
			TileDirs = append(TileDirs, i)
		}
	}
	return TileDirs
}
func Call4WayTile(ox, oy int, fn func(int, int) bool) []Direction_Type {
	TileDirs := []Direction_Type{}
	for i := Direction_Type(1); i <= 8; i += 2 {
		x, y := ox+i.Vector()[0], oy+i.Vector()[1]
		if fn(x, y) {
			TileDirs = append(TileDirs, i)
		}
	}
	return TileDirs
}
<commit_msg>hide attrib data<commit_after>// Copyright 2015,2016,2017,2018,2019 SeukWon Kang (kasworld@gmail.com)
// 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.

// 2d tile space direction
package direction

import (
	"math"

	"github.com/kasworld/go-abs"
)

type Direction_Type uint8

//go:generate stringer -type=Direction_Type
const (
	Dir_stop Direction_Type = iota
	Dir_n
	Dir_ne
	Dir_e
	Dir_se
	Dir_s
	Dir_sw
	Dir_w
	Dir_nw
	Direction_Count int = iota
)

func (i Direction_Type) String() string {
	return "Dir_" + attrib[i].Name
}

var attrib = []struct {
	Name string
	Vt   [2]int
	Len  float64
}{
	Dir_stop: {"Stop", [2]int{0, 0}, 0.0},
	Dir_n:    {"N", [2]int{0, -1}, 1.0},
	Dir_ne:   {"NE", [2]int{1, -1}, math.Sqrt2},
	Dir_e:    {"E", [2]int{1, 0}, 1.0},
	Dir_se:   {"SE", [2]int{1, 1}, math.Sqrt2},
	Dir_s:    {"S", [2]int{0, 1}, 1.0},
	Dir_sw:   {"SW", [2]int{-1, 1}, math.Sqrt2},
	Dir_w:    {"W", [2]int{-1, 0}, 1.0},
	Dir_nw:   {"NW", [2]int{-1, -1}, math.Sqrt2},
}

var vt2Dir = [3][3]Direction_Type{}

func init() {
	// println("init direction")
	for i, v := range attrib {
		vt2Dir[v.Vt[0]+1][v.Vt[1]+1] = Direction_Type(i)
	}
}

func (dt Direction_Type) Name() string {
	return attrib[dt].Name
}

func (dt Direction_Type) Vector() [2]int {
	return attrib[dt].Vt
}

func (dt Direction_Type) DxDy() (int, int) {
	return attrib[dt].Vt[0], attrib[dt].Vt[1]
}

func (dt Direction_Type) Dx() int {
	return attrib[dt].Vt[0]
}
func (dt Direction_Type) Dy() int {
	return attrib[dt].Vt[1]
}

func (dt Direction_Type) Len() float64 {
	return attrib[dt].Len
}

func (dt Direction_Type) TurnDir(turn int8) Direction_Type {
	if dt == Dir_stop {
		return dt
	}
	turn %= 8
	return Direction_Type((int8(dt)-1+turn+8)%8 + 1)
}

func (dt Direction_Type) ReverseDir() Direction_Type {
	vt := attrib[dt].Vt
	return Vt2Dir(-vt[0], -vt[1])
}
func (dt Direction_Type) InverseX() Direction_Type {
	vt := attrib[dt].Vt
	return Vt2Dir(-vt[0], vt[1])
}
func (dt Direction_Type) InverseY() Direction_Type {
	vt := attrib[dt].Vt
	return Vt2Dir(vt[0], -vt[1])
}

func (dt Direction_Type) IsValid() bool {
	return dt >= Dir_stop && dt <= Dir_nw
}

func (dt Direction_Type) Add(dt2 Direction_Type) Direction_Type {
	dx := dt.Dx() + dt2.Dx()
	dy := dt.Dy() + dt2.Dy()
	dx = abs.Signi(dx)
	dy = abs.Signi(dy)
	return Vt2Dir(dx, dy)
}

func (dt Direction_Type) MulXY(x, y int) (int, int) {
	return attrib[dt].Vt[0] * x, attrib[dt].Vt[1] * y
}

////

func Vt2Dir(x, y int) Direction_Type { // -1 ~ 1
	return vt2Dir[x+1][y+1]
}
func VtValidate(x, y int) bool {
	return x >= -1 && x <= 1 && y >= -1 && y <= 1
}

// func RandDir(rndfn func(st, ed int) int) Direction_Type {
// 	return Direction_Type(rndfn(1, 9))
// }

// find remote pos direction 8way
func DxDy2Dir8(dx, dy int) Direction_Type {
	dxs, dxv := abs.SignAbsi(dx)
	dys, dyv := abs.SignAbsi(dy)
	if dxv > dyv*2 {
		dys = 0
	}
	if dyv > dxv*2 {
		dxs = 0
	}
	return Vt2Dir(dxs, dys)
}

// find remote pos direction 4way
func DxDy2Dir4(dx, dy int) Direction_Type {
	dxs, dxv := abs.SignAbsi(dx)
	dys, dyv := abs.SignAbsi(dy)
	if dxv >= dyv {
		dys = 0
	} else {
		dxs = 0
	}
	return Vt2Dir(dxs, dys)
}

// contact only
func DxDy2Dir(dx, dy int) Direction_Type {
	dxs, _ := abs.SignAbsi(dx)
	dys, _ := abs.SignAbsi(dy)
	return Vt2Dir(dxs, dys)
}

// wrap dxdy
func CalcDxDyWrapped(dx, dy, xLen, yLen int) (int, int) {
	xs, xa := abs.SignAbsi(dx)
	if xa > xLen/2 {
		dx = xs * (xa - xLen)
	}
	ys, ya := abs.SignAbsi(dy)
	if ya > yLen/2 {
		dy = ys * (ya - yLen)
	}
	return dx, dy
}

// iscontact and dir with wrap
func CalcContactDirWrapped(from, to [2]int, xLen, yLen int) (bool, Direction_Type) {
	dx, dy := CalcDxDyWrapped(to[0]-from[0], to[1]-from[1], xLen, yLen)
	contact := VtValidate(dx, dy)
	if !contact {
		return false, 0
	}
	return true, Vt2Dir(dx, dy)
}

func CalcContactDirWrappedXY(x1, y1, x2, y2, xLen, yLen int) (bool, Direction_Type) {
	dx, dy := CalcDxDyWrapped(x2-x1, y2-y1, xLen, yLen)
	contact := VtValidate(dx, dy)
	if !contact {
		return false, 0
	}
	return true, Vt2Dir(dx, dy)
}

func Call8WayTile(ox, oy int, fn func(int, int) bool) []Direction_Type {
	TileDirs := []Direction_Type{}
	for i := Direction_Type(1); i <= 8; i++ {
		x, y := ox+i.Vector()[0], oy+i.Vector()[1]
		if fn(x, y) {
			TileDirs = append(TileDirs, i)
		}
	}
	return TileDirs
}
func Call4WayTile(ox, oy int, fn func(int, int) bool) []Direction_Type {
	TileDirs := []Direction_Type{}
	for i := Direction_Type(1); i <= 8; i += 2 {
		x, y := ox+i.Vector()[0], oy+i.Vector()[1]
		if fn(x, y) {
			TileDirs = append(TileDirs, i)
		}
	}
	return TileDirs
}
<|endoftext|>


<commit_before>package dnav

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"strconv"
	"strings"
	"time"
)

const (
	MainRootVar     = "MAINROOT"
	MainDumpVar     = "DUMPROOT"
	DefaultRoot     = "/newage/NEWAGE"
	DefaultRootName = "NEWAGE"
	DefaultDump     = "/dump"
)

var Debug bool

//A DumpDate represents a path in the Dump in an abstract way,
// by a description of the time. It is an opaque type without exported fields
type DumpDate struct {
	years  int
	months int
	days   int
	hours  int //1100 so the hours are /100 and minutes %100
}

//NewDumpDate creates a new DumpDate from the description.
func NewDumpDate(years int, months int, days int, hours int) *DumpDate {
	return &DumpDate{years, months, days, hours}
}

func (d *DumpDate) SameYear(d2 *DumpDate) bool {
	return d.years == d2.years
}

func (d *DumpDate) SameMonth(d2 *DumpDate) bool {
	return d.months == d2.months
}

func (d *DumpDate) SameDay(d2 *DumpDate) bool {
	return d.days == d2.days
}
func (d *DumpDate) SameHour(d2 *DumpDate) bool {
	return d.hours == d2.hours
}

func (d *DumpDate) String() string {
	return fmt.Sprintf("y:%d m:%d d:%d h:%d", d.years, d.months, d.days, d.hours)
}

func Dprintf(format string, a ...interface{}) (n int, err error) {

	if !Debug {
		return 0, nil
	}
	return fmt.Fprintf(os.Stderr, "dnav: "+format, a...)
}

//only for offset...
func SumDates(d1 DumpDate, d2 DumpDate) (ds DumpDate) {
	ds.years = d1.years + d2.years
	ds.months = d1.months + d2.months
	ds.days = d1.days + d2.days
	ds.hours = d1.hours + d2.hours
	return ds
}

//just to make a comparison, needs to be monotonic
func (d *DumpDate) minsApprox() int {
	ms := d.years*12 + d.months
	ds := ms*31 + d.days
	hs := ds*24 + d.hours/100
	mins := hs*60 + d.hours%100
	return mins
}

func (d *DumpDate) IsBefore(d2 DumpDate) bool {
	mins := d.minsApprox()
	mins2 := d2.minsApprox()
	return mins < mins2
}

func (d *DumpDate) IsAfter(d2 DumpDate) bool {
	mins := d.minsApprox()
	mins2 := d2.minsApprox()
	return mins > mins2
}

//Convert a time into a DumpDate
func TInDumpDate(t time.Time) DumpDate {
	return DumpDate{t.Year(), int(t.Month()), t.Day(), t.Hour()*100 + t.Minute()}
}

//Add DumpDate to a time to obtain a DumpDate
func TimeAddDate(t time.Time, deltaT DumpDate) DumpDate {
	t2 := t.AddDate(deltaT.years, deltaT.months, deltaT.days)
	hours := time.Duration(deltaT.hours) * time.Hour
	t2 = t2.Add(hours)
	return TInDumpDate(t2)
}

//for a set of paths separated by colons find the first that exists
func firstExists(paths string) string {
	Dprintf("checking [%s]\n", paths)
	lst := strings.Split(paths, ":")
	for _, path := range lst {
		var err error
		if _, err = os.Stat(path); err == nil {
			return path
		}
		Dprintf("[%s] does not exist: %s\n", path, err)
	}
	return ""
}

type Roots struct {
	MainRoot string
	DumpRoot string
	RootName string
}

//RdRoots finds the first path which exists for each of the environment variables.
//If none exist, it sets the default values.
func RdRoots(roots *Roots) {
	mR := os.Getenv(MainRootVar)
	mD := os.Getenv(MainDumpVar)

	if roots.MainRoot = firstExists(mR); roots.MainRoot == "" {
		roots.MainRoot = DefaultRoot
	}

	if roots.DumpRoot = firstExists(mD); roots.DumpRoot == "" {
		roots.DumpRoot = DefaultDump
	}

	if roots.RootName = path.Base(roots.MainRoot); roots.RootName == "" {
		roots.RootName = DefaultRootName
	}
}

//IsDump finds if a path belongs to the dump
func IsDump(path string, roots Roots) bool {
	return strings.HasPrefix(path, roots.DumpRoot)
}

//ParseDumpPath interprets a dump path as a date (years, months, days, hours). The smallest
//values may be missing and interpeted as zero.
func ParseDumpPath(path string, roots Roots) (d DumpDate, err error) {
	pathInDump := strings.TrimPrefix(path, roots.DumpRoot)

	if len(pathInDump) != len(path)-len(roots.DumpRoot) {
		return d, errors.New("bad root")
	}
	lstNames := strings.Split(pathInDump, "/")
	nNames := len(lstNames)
	if nNames > 0 {
		if d.years, err = strconv.Atoi(lstNames[1]); err != nil {
			if lstNames[1] != roots.RootName {
				return d, errors.New("bad year")
			}
		}
	}
	if nNames > 1 {
		if d.months, err = strconv.Atoi(lstNames[2]); err != nil {
			if lstNames[1] != roots.RootName {
				return d, errors.New("bad month")
			}
		}
		d.days = d.months % 100
		d.months = d.months / 100
	}
	if nNames > 2 {
		if d.hours, err = strconv.Atoi(lstNames[3]); err != nil {
			if lstNames[1] != roots.RootName {
				return d, errors.New("bad hour")
			}
		}
	}
	Dprintf("dump date %s\n", &d)
	return d, err
}

//given a path, find the biggest numeric name smaller than a number
func biggestSmallerEqthan(path string, max int) (curr int, err error) {
	var files []os.FileInfo
	Dprintf("big smaller than %d, %s\n", max, path)
	files, err = ioutil.ReadDir(path)
	if err != nil {
		return -1, err
	}
	curr = -1
	//they are sorted by filename
	for _, file := range files {
		switch file.Name() {
		case "current", "current_chk", "first", "lost+found":
			continue
		default:
			break
		}
		n := 0
		if n, err = strconv.Atoi(file.Name()); err == nil {
			//Dprintf("n:%d, max:%d curr: %d\n", n, max, curr)
			if n <= max && n >= curr {
				curr = n
			}
		} else {
			break
		}
	}
	if err != nil || curr < 0 {
		return -1, errors.New("non numeric file name")
	}
	Dprintf("big smaller than is: %d\n", curr)
	return curr, nil
}

//FindDumpPath looks for a path as close as possible to the
//date, but which may be equal or smaller
func FindDumpPath(d DumpDate, roots Roots) string {
	partial := roots.DumpRoot
	year, err := biggestSmallerEqthan(partial, d.years)
	if err != nil {
		return partial
	}
	partial += "/" + fmt.Sprintf("%4.4d", year)

	month, err := biggestSmallerEqthan(partial, 100*d.months+d.days)
	if err != nil {
		return partial
	}
	partial += "/" + fmt.Sprintf("%4.4d", month)

	hour, err := biggestSmallerEqthan(partial, d.hours)
	if err != nil {
		return partial
	}
	partial += "/" + fmt.Sprintf("%4.4d", hour)
	return partial
}
<commit_msg>fix index error in path, hour /100 error<commit_after>package dnav

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"strconv"
	"strings"
	"time"
)

const (
	MainRootVar     = "MAINROOT"
	MainDumpVar     = "DUMPROOT"
	DefaultRoot     = "/newage/NEWAGE"
	DefaultRootName = "NEWAGE"
	DefaultDump     = "/dump"
)

var Debug bool

//A DumpDate represents a path in the Dump in an abstract way,
// by a description of the time. It is an opaque type without exported fields
type DumpDate struct {
	years  int
	months int
	days   int
	hours  int //1100 so the hours are /100 and minutes %100
}

//NewDumpDate creates a new DumpDate from the description.
func NewDumpDate(years int, months int, days int, hours int) *DumpDate {
	return &DumpDate{years, months, days, hours}
}

func (d *DumpDate) SameYear(d2 *DumpDate) bool {
	return d.years == d2.years
}

func (d *DumpDate) SameMonth(d2 *DumpDate) bool {
	return d.months == d2.months
}

func (d *DumpDate) SameDay(d2 *DumpDate) bool {
	return d.days == d2.days
}
func (d *DumpDate) SameHour(d2 *DumpDate) bool {
	return d.hours == d2.hours
}

func (d *DumpDate) String() string {
	return fmt.Sprintf("y:%d m:%d d:%d h:%d", d.years, d.months, d.days, d.hours)
}

func Dprintf(format string, a ...interface{}) (n int, err error) {

	if !Debug {
		return 0, nil
	}
	return fmt.Fprintf(os.Stderr, "dnav: "+format, a...)
}

//only for offset...
func SumDates(d1 DumpDate, d2 DumpDate) (ds DumpDate) {
	ds.years = d1.years + d2.years
	ds.months = d1.months + d2.months
	ds.days = d1.days + d2.days
	ds.hours = d1.hours + d2.hours
	return ds
}

//just to make a comparison, needs to be monotonic
func (d *DumpDate) minsApprox() int {
	ms := d.years*12 + d.months
	ds := ms*31 + d.days
	hs := ds*24 + d.hours/100
	mins := hs*60 + d.hours%100
	return mins
}

func (d *DumpDate) IsBefore(d2 DumpDate) bool {
	mins := d.minsApprox()
	mins2 := d2.minsApprox()
	return mins < mins2
}

func (d *DumpDate) IsAfter(d2 DumpDate) bool {
	mins := d.minsApprox()
	mins2 := d2.minsApprox()
	return mins > mins2
}

//Convert a time into a DumpDate
func TInDumpDate(t time.Time) DumpDate {
	return DumpDate{t.Year(), int(t.Month()), t.Day(), t.Hour()*100 + t.Minute()}
}

//Add DumpDate to a time to obtain a DumpDate
func TimeAddDate(t time.Time, deltaT DumpDate) DumpDate {
	t2 := t.AddDate(deltaT.years, deltaT.months, deltaT.days)
	hours := time.Duration(deltaT.hours/100) * time.Hour
	t2 = t2.Add(hours)
	return TInDumpDate(t2)
}

//for a set of paths separated by colons find the first that exists
func firstExists(paths string) string {
	Dprintf("checking [%s]\n", paths)
	lst := strings.Split(paths, ":")
	for _, path := range lst {
		var err error
		if _, err = os.Stat(path); err == nil {
			return path
		}
		Dprintf("[%s] does not exist: %s\n", path, err)
	}
	return ""
}

type Roots struct {
	MainRoot string
	DumpRoot string
	RootName string
}

//RdRoots finds the first path which exists for each of the environment variables.
//If none exist, it sets the default values.
func RdRoots(roots *Roots) {
	mR := os.Getenv(MainRootVar)
	mD := os.Getenv(MainDumpVar)

	if roots.MainRoot = firstExists(mR); roots.MainRoot == "" {
		roots.MainRoot = DefaultRoot
	}

	if roots.DumpRoot = firstExists(mD); roots.DumpRoot == "" {
		roots.DumpRoot = DefaultDump
	}

	if roots.RootName = path.Base(roots.MainRoot); roots.RootName == "" {
		roots.RootName = DefaultRootName
	}
}

//IsDump finds if a path belongs to the dump
func IsDump(path string, roots Roots) bool {
	return strings.HasPrefix(path, roots.DumpRoot)
}

//ParseDumpPath interprets a dump path as a date (years, months, days, hours). The smallest
//values may be missing and interpeted as zero.
func ParseDumpPath(path string, roots Roots) (d DumpDate, err error) {
	pathInDump := strings.TrimPrefix(path, roots.DumpRoot)

	if len(pathInDump) != len(path)-len(roots.DumpRoot) {
		return d, errors.New("bad root")
	}
	lstNames := strings.Split(pathInDump, "/")
	nNames := len(lstNames)
	if nNames > 1 {
		if d.years, err = strconv.Atoi(lstNames[1]); err != nil {
			if lstNames[1] != roots.RootName {
				return d, errors.New("bad year")
			}
		}
	}
	if nNames > 2 {
		if d.months, err = strconv.Atoi(lstNames[2]); err != nil {
			if lstNames[1] != roots.RootName {
				return d, errors.New("bad month")
			}
		}
		d.days = d.months % 100
		d.months = d.months / 100
	}
	if nNames > 3 {
		if d.hours, err = strconv.Atoi(lstNames[3]); err != nil {
			if lstNames[1] != roots.RootName {
				return d, errors.New("bad hour")
			}
		}
	}
	return d, err
}

//given a path, find the biggest numeric name smaller than a number
func biggestSmallerEqthan(path string, max int) (curr int, err error) {
	var files []os.FileInfo
	Dprintf("big smaller than %d, %s\n", max, path)
	files, err = ioutil.ReadDir(path)
	if err != nil {
		return -1, err
	}
	curr = -1
	//they are sorted by filename
	for _, file := range files {
		switch file.Name() {
		case "current", "current_chk", "first", "lost+found":
			continue
		default:
			break
		}
		n := 0
		if n, err = strconv.Atoi(file.Name()); err == nil {
			//Dprintf("n:%d, max:%d curr: %d\n", n, max, curr)
			if n <= max && n >= curr {
				curr = n
			}
		} else {
			break
		}
	}
	if err != nil || curr < 0 {
		return -1, errors.New("non numeric file name")
	}
	Dprintf("big smaller than is: %d\n", curr)
	return curr, nil
}

//FindDumpPath looks for a path as close as possible to the
//date, but which may be equal or smaller
func FindDumpPath(d DumpDate, roots Roots) string {
	partial := roots.DumpRoot
	year, err := biggestSmallerEqthan(partial, d.years)
	if err != nil {
		return partial
	}
	partial += "/" + fmt.Sprintf("%4.4d", year)

	month, err := biggestSmallerEqthan(partial, 100*d.months+d.days)
	if err != nil {
		return partial
	}
	partial += "/" + fmt.Sprintf("%4.4d", month)

	hour, err := biggestSmallerEqthan(partial, d.hours)
	if err != nil {
		return partial
	}
	partial += "/" + fmt.Sprintf("%4.4d", hour)
	return partial
}
<|endoftext|>


<commit_before>package conf

import (
	"encoding/json"
	"io/ioutil"
	"log"
	"net/url"
	"os"
	"strings"
)

type Config struct {
	RootURL  string `mapstructure:"root_url"`
	Port     int    `mapstructure:"port"`
	DataPath string `mapstructure:"data_path"`
}

var C Config

// Load reads the configuration file from disk, if present
func init() {
	file, err := ioutil.ReadFile("conf/app.json")
	if err != nil && os.IsNotExist(err) {
		C.RootURL = "http://localhost:4000/"
		C.Port = 4000
		C.DataPath = "data"
		return
	} else if err != nil {
		log.Fatal("Failed to read in app.json", err)
	}

	err = json.Unmarshal(file, &C)
	if err != nil {
		log.Fatal("Failed to marshal configuration settings", err)
	}
}

// GetRootURLPath returns just the path portion of the RootUrl value,
// without any trailing slashes.
func (c *Config) GetRootURLPath() string {
	// Check if root url has a sub-path
	url, err := url.Parse(c.RootURL)
	if err != nil {
		panic("Invalid root_url")
	}
	return strings.TrimSuffix(url.Path, "/")
}
<commit_msg>When reading the app.json configuration file, any values not present in the file will now stay with the default value.<commit_after>package conf

import (
	"encoding/json"
	"io/ioutil"
	"log"
	"net/url"
	"os"
	"strings"
)

// Config encapsules all the configuration settings available to the application
type Config struct {
	RootURL  string `json:"root_url"`
	Port     int    `json:"port"`
	DataPath string `json:"data_path"`
}

// C is an instance of Config that holds the values read from the configuration
// file on disk, or default values.
var C = Config{
	RootURL:  "http://localhost:4000/",
	Port:     4000,
	DataPath: "data",
}

func init() {
	file, err := ioutil.ReadFile("conf/app.json")
	if err != nil && !os.IsNotExist(err) {
		return
	} else if err != nil {
		log.Fatalf("Failed to read in app.json. Error = %s", err)
	}

	err = json.Unmarshal(file, &C)
	if err != nil {
		log.Fatalf("Failed to marshal configuration settings. Error = %s", err)
	}
}

// GetRootURLPath returns just the path portion of the RootUrl value,
// without any trailing slashes.
func (c *Config) GetRootURLPath() string {
	// Check if root url has a sub-path
	url, err := url.Parse(c.RootURL)
	if err != nil {
		panic("Invalid root_url")
	}
	return strings.TrimSuffix(url.Path, "/")
}
<|endoftext|>


<commit_before>// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package message

var (
	_ InboundMessage  = &inboundMessage{}
	_ OutboundMessage = &outboundMessage{}
)

// InboundMessage represents a set of fields for an inbound message that can be serialized into a byte stream
type InboundMessage interface {
	BytesSavedCompression() int
	Op() Op
	Bytes() []byte
	Get(Field) interface{}
}

type inboundMessage struct {
	op                    Op
	bytes                 []byte
	bytesSavedCompression int
	fields                map[Field]interface{}
}

// OutboundMessage represents a set of fields for an outbound message that can be serialized into a byte stream
type OutboundMessage interface {
	BytesSavedCompression() int
	Bytes() []byte
	Op() Op
}

type outboundMessage struct {
	bytes                 []byte
	bytesSavedCompression int
	op                    Op
}

// Op returns the value of the specified operation in this message
func (inMsg *inboundMessage) Op() Op { return inMsg.op }

// Bytes returns this message in bytes
func (inMsg *inboundMessage) Bytes() []byte { return inMsg.bytes }

// BytesSavedCompression returns the number of bytes this message saved due to
// compression. That is, the number of bytes we did not receive over the
// network due to the message being compressed. Empty slice for messages that were not
// compressed.
func (inMsg *inboundMessage) BytesSavedCompression() int { return inMsg.bytesSavedCompression }

// Field returns the value of the specified field in this message
func (inMsg *inboundMessage) Get(field Field) interface{} { return inMsg.fields[field] }

// Op returns the value of the specified operation in this message
func (outMsg *outboundMessage) Op() Op { return outMsg.op }

// Bytes returns this message in bytes
func (outMsg *outboundMessage) Bytes() []byte { return outMsg.bytes }

// BytesSavedCompression returns the number of bytes this message saved due to
// compression. That is, the number of bytes we did not send over the
// network due to the message being compressed. Empty slice for messages that were not
// compressed.
func (outMsg *outboundMessage) BytesSavedCompression() int { return outMsg.bytesSavedCompression }
<commit_msg>chore: inbound / outbound<commit_after>// (c) 2019-2020, Ava Labs, Inc. All rights reserved.
// See the file LICENSE for licensing terms.

package message

var (
	_ InboundMessage  = &inboundMessage{}
	_ OutboundMessage = &outboundMessage{}
)

// InboundMessage represents a set of fields for an inbound message that can be serialized into a byte stream
type InboundMessage interface {
	BytesSavedCompression() int
	Op() Op
	Bytes() []byte
	Get(Field) interface{}
}

type inboundMessage struct {
	op                    Op
	bytes                 []byte
	bytesSavedCompression int
	fields                map[Field]interface{}
}

// OutboundMessage represents a set of fields for an outbound message that can be serialized into a byte stream
type OutboundMessage interface {
	BytesSavedCompression() int
	Bytes() []byte
	Op() Op
}

type outboundMessage struct {
	bytes                 []byte
	bytesSavedCompression int
	op                    Op
}

// Op returns the value of the specified operation in this message
func (inMsg *inboundMessage) Op() Op { return inMsg.op }

// Bytes returns this message in bytes
func (inMsg *inboundMessage) Bytes() []byte { return inMsg.bytes }

// BytesSavedCompression returns the number of bytes this message saved due to
// compression. That is, the number of bytes we did not receive over the
// network due to the message being compressed. 0 for messages that were not
// compressed.
func (inMsg *inboundMessage) BytesSavedCompression() int { return inMsg.bytesSavedCompression }

// Field returns the value of the specified field in this message
func (inMsg *inboundMessage) Get(field Field) interface{} { return inMsg.fields[field] }

// Op returns the value of the specified operation in this message
func (outMsg *outboundMessage) Op() Op { return outMsg.op }

// Bytes returns this message in bytes
func (outMsg *outboundMessage) Bytes() []byte { return outMsg.bytes }

// BytesSavedCompression returns the number of bytes this message saved due to
// compression. That is, the number of bytes we did not send over the
// network due to the message being compressed. 0 for messages that were not
// compressed.
func (outMsg *outboundMessage) BytesSavedCompression() int { return outMsg.bytesSavedCompression }
<|endoftext|>


<commit_before>package repl

import (
	"fmt"
	"github.com/GeertJohan/go.linenoise"
	"github.com/vifino/luar"
	"github.com/vifino/golua/lua"
	"strings"
)

const (
	LUA_PROMPT1 = "> "
	LUA_PROMPT2 = ">> "
	BANNER = `
  ⡎⠑ | Carbon
  ⠣⠔ | (c) 2015 vifino
`
)

// modified https://github.com/vifino/luar/blob/master/examples/luar.go

func Run(L *lua.State) {
	err := L.DoString(lua_code)
	if err != nil {
		fmt.Println("initial " + err.Error())
		return
	}
	// particularly the completion logic
	complete := luar.NewLuaObjectFromName(L, "lua_candidates")
	// this function returns a string slice of candidates
	str_slice := luar.Types([]string{})

	linenoise.SetCompletionHandler(func(in string) []string {
		val, err := complete.Callf(str_slice, in)
		if err != nil || len(val) == 1 && val[0] == nil {
			return []string{}
		} else {
			return val[0].([]string)
		}
	})

	fmt.Println(BANNER)
	L.DoString(`print(jit.version)`)
	fmt.Println("")

	prompt := LUA_PROMPT1
	code := ""

	for {
		// ctrl-C/ctrl-D handling with latest update of go.linenoise
		str, err := linenoise.Line(prompt)
		if err != nil {
			return
		}
		if len(str) > 0 {
			linenoise.AddHistory(str)
			if str[0] == '=' || str[0] == '.' {
				exprs := str[1:]
				if str[0] == '=' {
					str = "prettify(" + exprs + ")"
				} else {
					str = "print(" + exprs + ")"
				}
			}
			continuing := false
			code = code + str
			//fmt.Println("'"+code+"'")
			err := L.DoString(code)
			if err != nil {
				errs := err.Error()
				// fmt.Println("err",errs)
				// strip line nonsense if error occurred on prompt
				idx := strings.Index(errs, ": ")
				if idx > -1 && strings.HasPrefix(errs, "[string ") {
					errs = errs[idx+2:]
				}
				// need to keep collecting line?
				if strings.HasSuffix(errs, "near '<eof>'") {
					continuing = true
				} else {
					fmt.Println(errs)
				}
			}
			if continuing { // prompt must reflect continuing state
				prompt = LUA_PROMPT2
				code = code + "\n"
			} else {
				prompt = LUA_PROMPT1
				code = ""
			}
		}
	}
}

// pretty-printing and code completion logic in Lua
const lua_code = `
local tostring = tostring
local append = table.insert
local function quote (v)
	local t = type(v)
	if t == 'string' then
		return ('%q'):format(v)
	elseif t == 'function' then
		return '<fun>'
	elseif t == 'userdata' then
		return '<udata>'
	else
		return tostring(v)
	end
end
local dump
dump = function(t, options)
	options = options or { }
	local limit = options.limit or 1000
	local buff = {tables={}}
	if type(t) == 'table' then
			buff.tables[t] = true
	end
	local k, tbuff = 1, nil
	local function put(v)
		buff[k] = v
		k = k + 1
	end
	local function put_value(value)
		if type(value) ~= 'table' then
			put(quote(value))
			if limit and k > limit then
				buff[k] = "..."
				error("buffer overrun")
			end
		else
			if not buff.tables[value] then
				buff.tables[value] = true
				tbuff(value)
			else
				put("<cycle>")
			end
		end
		return put(',')
	end
	function tbuff(t)
		local mt
		if not (options.raw) then
			mt = getmetatable(t)
		end
		if mt and mt.__tostring then
			return put(quote(t))
		elseif type(t) ~= 'table' and not (mt and mt.__pairs) then
			return put(quote(t))
		else
			put('{')
			local mt_pairs, indices = mt and mt.__pairs
			if not mt_pairs and #t > 0 then
				indices = {}
				for i = 1, #t do
					indices[i] = true
				end
			end
			for key, value in pairs(t) do
				local _continue_0 = false
				repeat
					if indices and indices[key] then
						_continue_0 = true
						break
					end
					if type(key) ~= 'string' then
						key = '[' .. tostring(key) .. ']'
					elseif key:match('%s') then
						key = quote(key)
					end
					put(key .. '=')
					put_value(value)
					_continue_0 = true
				until true
				if not _continue_0 then
					break
				end
			end
			if indices then
				local _list_0 = t
				for _index_0 = 1, #_list_0 do
					local v = _list_0[_index_0]
					put_value(v)
				end
			end
			if buff[k - 1] == "," then
				k = k - 1
			end
			return put('}')
		end
	end
	tbuff(t)
	--pcall(tbuff, t)
	return table.concat(buff)
end

function prettify(...) -- Almost JSON(tm)
	local out = ""
	local objs = {...}
	local function prettifyrecursive(obj, tab, skipfirstindent)
		local tab = tab or 0
		local tabify = function(s) for i=1,tab do out = out .. ' ' end out = out .. s end
		local line = function(s) tabify(s .. '\n') end
		local mt = getmetatable(obj)
		if mt and mt.__tostring then
			out = out .. tostring(obj)
		else
			if not skipfirstindent then
				line('{')
			else
				out = out .. '{\n'
			end
			tab = tab+2
			for k,v in pairs(obj) do
				if type(v) == 'table' then
					if tab > 16 or next(v) == nil then
						line(k .. ': ' .. tostring(v))
					else
						tabify(k .. ': ')
						prettifyrecursive(v, tab + #k + 2, true)
					end
				else
					line(k .. ': ' .. tostring(v))
				end
			end
			tab = tab-2
			line('}')
		end
	end
	for i = 1,select('#',...) do
		local obj = select(i,...)
		if type(obj) ~= 'table' then
			if type(obj) == 'userdata' or type(obj) == 'cdata' then
				out = out .. tostring(obj) .. '\n'
			else
				out = out .. tostring(obj) .. '\n'
			end
		else
			prettifyrecursive(obj)
		end
	end
	return out:sub(1, -2)
end

local append = table.insert
local function is_pair_iterable(t)
		local mt = getmetatable(t)
		return type(t) == 'table' or (mt and mt.__pairs)
end

function lua_candidates(line)
	-- identify the expression!
	local res = {}
	local i1,i2 = line:find('[.:%w_]+$')
	if not i1 then return res end
	local front,partial = line:sub(1,i1-1), line:sub(i1)
	local prefix, last = partial:match '(.-)([^.:]*)$'
	local t, all = _G
	if #prefix > 0 then
		local P = prefix:sub(1,-2)
		all = last == ''
		for w in P:gmatch '[^.:]+' do
			t = t[w]
			if not t then
				return res
			end
		end
	end
	prefix = front .. prefix
	local function append_candidates(t)
		for k,v in pairs(t) do
			if all or k:sub(1,#last) == last then
				append(res,prefix..k)
			end
		end
	end
	local mt = getmetatable(t)
	if is_pair_iterable(t) then
		append_candidates(t)
	end
	if mt and is_pair_iterable(mt.__index) then
		append_candidates(mt.__index)
	end
	return res
end

--// override struct __pairs for code completion
local function sdump(st)
		local t = luar.type(st)
		local val = luar.value(st)
		local nm = t.NumMethod()
		local mt = t --// type to dispatch methods on ptr receiver
		if t.Kind() == 22 then --// pointer!
				t = t.Elem()
				val = val.Elem()
		end
		local n = t.NumField()
		local cc = {}
		for i = 1,n do
				local f,v = t.Field(i-1)
				if f.PkgPath == "" then --// only public fields!
						v = val.Field(i-1)
						cc[f.Name] = v.Interface()
				end
		end
		--// then public methods...
		for i = 1,nm do
				local m = mt.Method(i-1)
				if m.PkgPath == "" then --// again, only public
						cc[m.Name] = true
				end
		end
		return cc
end

mt = getmetatable(__DUMMY__)
mt.__pairs = function(st)
		local cc = sdump(st)
		return pairs(cc)
end

`
<commit_msg>Lets try this.<commit_after>package repl

import (
	"fmt"
	"github.com/GeertJohan/go.linenoise"
	"github.com/vifino/luar"
	"github.com/vifino/golua/lua"
	"strings"
)

const (
	LUA_PROMPT1 = "> "
	LUA_PROMPT2 = ">> "
	BANNER = `
  ⡎⠑ | Carbon
  ⠣⠔ | (c) 2015 vifino
`
)

// modified https://github.com/vifino/luar/blob/master/examples/luar.go

func Run(L *lua.State) {
	err := L.DoString(lua_code)
	if err != nil {
		fmt.Println("initial " + err.Error())
		return
	}
	// particularly the completion logic
	complete := luar.NewLuaObjectFromName(L, "lua_candidates")
	// this function returns a string slice of candidates
	str_slice := luar.Types([]string{})

	linenoise.SetCompletionHandler(func(in string) []string {
		val, err := complete.Callf(str_slice, in)
		if err != nil || len(val) == 1 && val[0] == nil {
			return []string{}
		} else {
			return val[0].([]string)
		}
	})

	fmt.Println(BANNER)
	L.DoString(`print(jit.version)`)
	fmt.Println("")

	prompt := LUA_PROMPT1
	code := ""

	for {
		// ctrl-C/ctrl-D handling with latest update of go.linenoise
		str, err := linenoise.Line(prompt)
		if err != nil {
			return
		}
		if len(str) > 0 {
			linenoise.AddHistory(str)
			if str[0] == '=' || str[0] == '.' {
				exprs := str[1:]
				if str[0] == '=' {
					str = "prettify(" + exprs + ")"
				} else {
					str = "print(" + exprs + ")"
				}
			}
			continuing := false
			code = code + str
			//fmt.Println("'"+code+"'")
			err := L.DoString(code)
			if err != nil {
				errs := err.Error()
				// fmt.Println("err",errs)
				// strip line nonsense if error occurred on prompt
				idx := strings.Index(errs, ": ")
				if idx > -1 && strings.HasPrefix(errs, "[string ") {
					errs = errs[idx+2:]
				}
				// need to keep collecting line?
				if strings.HasSuffix(errs, "near '<eof>'") {
					continuing = true
				} else {
					fmt.Println(errs)
				}
			}
			if continuing { // prompt must reflect continuing state
				prompt = LUA_PROMPT2
				code = code + "\n"
			} else {
				prompt = LUA_PROMPT1
				code = ""
			}
		}
	}
}

// pretty-printing and code completion logic in Lua
const lua_code = `
local tostring = tostring
local append = table.insert
local function quote (v)
	local t = type(v)
	if t == 'string' then
		return ('%q'):format(v)
	elseif t == 'function' then
		return '<fun>'
	elseif t == 'userdata' then
		return '<udata>'
	else
		return tostring(v)
	end
end
local dump
dump = function(t, options)
	options = options or { }
	local limit = options.limit or 1000
	local buff = {tables={}}
	if type(t) == 'table' then
			buff.tables[t] = true
	end
	local k, tbuff = 1, nil
	local function put(v)
		buff[k] = v
		k = k + 1
	end
	local function put_value(value)
		if type(value) ~= 'table' then
			put(quote(value))
			if limit and k > limit then
				buff[k] = "..."
				error("buffer overrun")
			end
		else
			if not buff.tables[value] then
				buff.tables[value] = true
				tbuff(value)
			else
				put("<cycle>")
			end
		end
		return put(',')
	end
	function tbuff(t)
		local mt
		if not (options.raw) then
			mt = getmetatable(t)
		end
		if mt and mt.__tostring then
			return put(quote(t))
		elseif type(t) ~= 'table' and not (mt and mt.__pairs) then
			return put(quote(t))
		else
			put('{')
			local mt_pairs, indices = mt and mt.__pairs
			if not mt_pairs and #t > 0 then
				indices = {}
				for i = 1, #t do
					indices[i] = true
				end
			end
			for key, value in pairs(t) do
				local _continue_0 = false
				repeat
					if indices and indices[key] then
						_continue_0 = true
						break
					end
					if type(key) ~= 'string' then
						key = '[' .. tostring(key) .. ']'
					elseif key:match('%s') then
						key = quote(key)
					end
					put(key .. '=')
					put_value(value)
					_continue_0 = true
				until true
				if not _continue_0 then
					break
				end
			end
			if indices then
				local _list_0 = t
				for _index_0 = 1, #_list_0 do
					local v = _list_0[_index_0]
					put_value(v)
				end
			end
			if buff[k - 1] == "," then
				k = k - 1
			end
			return put('}')
		end
	end
	tbuff(t)
	--pcall(tbuff, t)
	return table.concat(buff)
end

function prettify(...) -- Almost JSON(tm)
	local out = ""
	local objs = {...}
	local function prettifyrecursive(obj, tab, skipfirstindent)
		local tab = tab or 0
		local tabify = function(s) for i=1,tab do out = out .. ' ' end out = out .. s end
		local line = function(s) tabify(s .. '\n') end
		local mt = getmetatable(obj)
		if mt and mt.__tostring then
			out = out .. tostring(obj)
		else
			if not skipfirstindent then
				line('{')
			else
				out = out .. '{\n'
			end
			tab = tab+2
			for k,v in pairs(obj) do
				if type(v) == 'table' then
					if tab > 16 or next(v) == nil then
						line(k .. ': ' .. tostring(v))
					else
						tabify(k .. ': ')
						prettifyrecursive(v, tab + #k + 2, true)
					end
				else
					line(k .. ': ' .. tostring(v))
				end
			end
			tab = tab-2
			line('}')
		end
	end
	for i = 1,select('#',...) do
		local obj = select(i,...)
		if type(obj) ~= 'table' then
			if type(obj) == 'userdata' or type(obj) == 'cdata' then
				out = out .. tostring(obj) .. '\n'
			else
				out = out .. tostring(obj) .. '\n'
			end
		else
			prettifyrecursive(obj)
		end
	end
	return out:sub(1, -2)
end

local append = table.insert
local function is_pair_iterable(t)
		local mt = getmetatable(t)
		return type(t) == 'table' or (mt and mt.__pairs)
end

function lua_candidates(line)
	-- identify the expression!
	local res = {}
	local i1,i2 = line:find('[.:%w_]+$')
	if not i1 then return res end
	local front,partial = line:sub(1,i1-1), line:sub(i1)
	local prefix, last = partial:match '(.-)([^.:]*)$'
	local t, all = _G
	if #prefix > 0 then
		local P = prefix:sub(1,-2)
		all = last == ''
		for w in P:gmatch '[^.:]+' do
			t = t[w]
			if not t then
				return res
			end
		end
	end
	prefix = front .. prefix
	local function append_candidates(t)
		for k,v in pairs(t) do
			if all or k:sub(1,#last) == last then
				append(res,prefix..k)
			end
		end
	end
	local mt = getmetatable(t)
	if is_pair_iterable(t) then
		append_candidates(t)
	end
	if mt and is_pair_iterable(mt.__index) then
		append_candidates(mt.__index)
	end
	return res
end

--// override struct __pairs for code completion
local function sdump(st)
		local t = luar.type(st)
		local val = luar.value(st)
		local nm = t.NumMethod()
		local mt = t --// type to dispatch methods on ptr receiver
		if t.Kind() == 22 then --// pointer!
				t = t.Elem()
				val = val.Elem()
		end
		local n = t.NumField()
		local cc = {}
		for i = 1,n do
				local f,v = t.Field(i-1)
				if f.PkgPath == "" then --// only public fields!
						v = val.Field(i-1)
						cc[f.Name] = v.Interface()
				end
		end
		--// then public methods...
		for i = 1,nm do
				local m = mt.Method(i-1)
				if m.PkgPath == "" then --// again, only public
						cc[m.Name] = true
				end
		end
		return cc
end

mt = getmetatable(_G)
mt.__pairs = function(st)
		local cc = sdump(st)
		return pairs(cc)
end

`
<|endoftext|>


<commit_before>// Copyright (C) 2016  Arista Networks, Inc.
// Use of this source code is governed by the Apache License 2.0
// that can be found in the COPYING file.

// Package monotime provides a fast monotonic clock source.
package monotime

import (
	_ "unsafe" // required to use //go:linkname
)

//go:noescape
//go:linkname nanotime runtime.nanotime
func nanotime() int64

// Now returns the current time in nanoseconds from a monotonic clock.
// The time returned is based on some arbitrary platform-specific point in the
// past.  The time returned is guaranteed to increase monotonically at a
// constant rate, unlike time.Now() from the Go standard library, which may
// slow down, speed up, jump forward or backward, due to NTP activity or leap
// seconds.
func Now() uint64 {
	return uint64(nanotime())
}
<commit_msg>monotime: add Since function<commit_after>// Copyright (C) 2016  Arista Networks, Inc.
// Use of this source code is governed by the Apache License 2.0
// that can be found in the COPYING file.

// Package monotime provides a fast monotonic clock source.
package monotime

import (
	"time"
	_ "unsafe" // required to use //go:linkname
)

//go:noescape
//go:linkname nanotime runtime.nanotime
func nanotime() int64

// Now returns the current time in nanoseconds from a monotonic clock.
// The time returned is based on some arbitrary platform-specific point in the
// past.  The time returned is guaranteed to increase monotonically at a
// constant rate, unlike time.Now() from the Go standard library, which may
// slow down, speed up, jump forward or backward, due to NTP activity or leap
// seconds.
func Now() uint64 {
	return uint64(nanotime())
}

// Since returns the amount of time that has elapsed since t. t should be
// the result of a call to Now() on the same machine.
func Since(t uint64) time.Duration {
	return time.Duration(Now() - t)
}
<|endoftext|>


<commit_before>package stats

import (
	"bytes"
	"net"
	"sync"
	"time"

	"github.com/raintank/worldping-api/pkg/log"
)

var (
	queueItems      *Range32
	genDataDuration *Gauge32
	flushDuration   *LatencyHistogram15s32
	messageSize     *Gauge32
	connected       *Bool
)

type GraphiteMetric interface {
	// Report the measurements in graphite format and reset measurements for the next interval if needed
	ReportGraphite(prefix []byte, buf []byte, now time.Time) []byte
}

type Graphite struct {
	prefix                   []byte
	addr                     string
	prefixCounter32          string
	prefixGauge32            string
	prefixGauge64            string
	prefixRange32            string
	prefixLatencyHistogram32 string

	toGraphite chan []byte
}

func NewGraphite(prefix, addr string, interval int, bufferSize int) {
	if len(prefix) != 0 && prefix[len(prefix)-1] != '.' {
		prefix = prefix + "."
	}
	NewGauge32("stats.graphite.write_queue.size").Set(bufferSize)
	queueItems = NewRange32("stats.graphite.write_queue.items")
	// metric stats.generate_message is how long it takes to generate the stats
	genDataDuration = NewGauge32("stats.generate_message.duration")
	flushDuration = NewLatencyHistogram15s32("stats.graphite.flush")
	messageSize = NewGauge32("stats.message_size")
	connected = NewBool("stats.graphite.connected")

	g := &Graphite{
		prefix:     []byte(prefix),
		addr:       addr,
		toGraphite: make(chan []byte, bufferSize),
	}
	go g.writer()
	go g.reporter(interval)
}

func (g *Graphite) reporter(interval int) {
	ticker := tick(time.Duration(interval) * time.Second)
	for now := range ticker {
		log.Debug("stats flushing for", now, "to graphite")
		queueItems.Value(len(g.toGraphite))
		if cap(g.toGraphite) != 0 && len(g.toGraphite) == cap(g.toGraphite) {
			// no space in buffer, no use in doing any work
			continue
		}

		pre := time.Now()

		buf := make([]byte, 0)

		var fullPrefix bytes.Buffer
		for name, metric := range registry.list() {
			fullPrefix.Reset()
			fullPrefix.Write(g.prefix)
			fullPrefix.WriteString(name)
			fullPrefix.WriteRune('.')
			buf = metric.ReportGraphite(fullPrefix.Bytes(), buf, now)
		}

		genDataDuration.Set(int(time.Since(pre).Nanoseconds()))
		messageSize.Set(len(buf))
		g.toGraphite <- buf
		queueItems.Value(len(g.toGraphite))
	}
}

// writer connects to graphite and submits all pending data to it
// TODO: conn.Write() returns no error for a while when the remote endpoint is down, the reconnect happens with a delay. this can also cause lost data for a second or two.
func (g *Graphite) writer() {
	connectTicker := time.Tick(time.Second)

	lock := &sync.Mutex{}
	var conn net.Conn

	var err error
	go func() {
		for range connectTicker {
			lock.Lock()
			if conn == nil {
				conn, err = net.Dial("tcp", g.addr)
				if err == nil {
					log.Info("stats now connected to %s", g.addr)
				} else {
					log.Warn("stats dialing %s failed: %s. will retry", g.addr, err.Error())
				}
			}
			lock.Unlock()
		}
	}()
	for buf := range g.toGraphite {
		queueItems.Value(len(g.toGraphite))
		var ok bool
		for !ok {
			for {
				lock.Lock()
				haveConn := (conn != nil)
				connected.Set(haveConn)
				lock.Unlock()
				if haveConn {
					break
				}
				time.Sleep(time.Second)
			}
			pre := time.Now()
			lock.Lock()
			_, err = conn.Write(buf)
			if err == nil {
				ok = true
				flushDuration.Value(time.Since(pre))
			} else {
				log.Warn("stats failed to write to graphite: %s (took %s). will retry...", err, time.Now().Sub(pre))
				conn.Close()
				conn = nil
				connected.SetFalse()
			}
			lock.Unlock()
		}
	}
	lock.Lock()
	if conn != nil {
		conn.Close()
	}
	lock.Unlock()
}
<commit_msg>simplify outbound to graphite conn routine<commit_after>package stats

import (
	"bytes"
	"net"
	"time"

	"github.com/raintank/worldping-api/pkg/log"
)

var (
	queueItems      *Range32
	genDataDuration *Gauge32
	flushDuration   *LatencyHistogram15s32
	messageSize     *Gauge32
	connected       *Bool
)

type GraphiteMetric interface {
	// Report the measurements in graphite format and reset measurements for the next interval if needed
	ReportGraphite(prefix []byte, buf []byte, now time.Time) []byte
}

type Graphite struct {
	prefix                   []byte
	addr                     string
	prefixCounter32          string
	prefixGauge32            string
	prefixGauge64            string
	prefixRange32            string
	prefixLatencyHistogram32 string

	toGraphite chan []byte
}

func NewGraphite(prefix, addr string, interval int, bufferSize int) {
	if len(prefix) != 0 && prefix[len(prefix)-1] != '.' {
		prefix = prefix + "."
	}
	NewGauge32("stats.graphite.write_queue.size").Set(bufferSize)
	queueItems = NewRange32("stats.graphite.write_queue.items")
	// metric stats.generate_message is how long it takes to generate the stats
	genDataDuration = NewGauge32("stats.generate_message.duration")
	flushDuration = NewLatencyHistogram15s32("stats.graphite.flush")
	messageSize = NewGauge32("stats.message_size")
	connected = NewBool("stats.graphite.connected")

	g := &Graphite{
		prefix:     []byte(prefix),
		addr:       addr,
		toGraphite: make(chan []byte, bufferSize),
	}
	go g.writer()
	go g.reporter(interval)
}

func (g *Graphite) reporter(interval int) {
	ticker := tick(time.Duration(interval) * time.Second)
	for now := range ticker {
		log.Debug("stats flushing for", now, "to graphite")
		queueItems.Value(len(g.toGraphite))
		if cap(g.toGraphite) != 0 && len(g.toGraphite) == cap(g.toGraphite) {
			// no space in buffer, no use in doing any work
			continue
		}

		pre := time.Now()

		buf := make([]byte, 0)

		var fullPrefix bytes.Buffer
		for name, metric := range registry.list() {
			fullPrefix.Reset()
			fullPrefix.Write(g.prefix)
			fullPrefix.WriteString(name)
			fullPrefix.WriteRune('.')
			buf = metric.ReportGraphite(fullPrefix.Bytes(), buf, now)
		}

		genDataDuration.Set(int(time.Since(pre).Nanoseconds()))
		messageSize.Set(len(buf))
		g.toGraphite <- buf
		queueItems.Value(len(g.toGraphite))
	}
}

// writer connects to graphite and submits all pending data to it
// TODO: conn.Write() returns no error for a while when the remote endpoint is down, the reconnect happens with a delay. this can also cause lost data for a second or two.
func (g *Graphite) writer() {
	connectTicker := time.Tick(time.Second)
	var conn net.Conn
	var err error

	assureConn := func() net.Conn {
		connected.Set(conn != nil)
		for conn == nil {
			time.Sleep(time.Second)
			conn, err = net.Dial("tcp", g.addr)
			if err == nil {
				log.Info("stats now connected to %s", g.addr)
			} else {
				log.Warn("stats dialing %s failed: %s. will retry", g.addr, err.Error())
			}
			connected.Set(conn != nil)
		}
		return conn
	}

	for {
		select {
		case <-connectTicker:
			conn = assureConn()
		case buf := <-g.toGraphite:
			queueItems.Value(len(g.toGraphite))
			var ok bool
			for !ok {
				conn = assureConn()
				pre := time.Now()
				_, err = conn.Write(buf)
				if err == nil {
					ok = true
					flushDuration.Value(time.Since(pre))
				} else {
					log.Warn("stats failed to write to graphite: %s (took %s). will retry...", err, time.Now().Sub(pre))
					conn.Close()
					conn = nil
				}
			}
		}
	}
}
<|endoftext|>