<commit_before>// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.

package glog

import (
	"bytes"
	"context"
	"fmt"
	"github.com/gogf/gf/container/gtype"
	"github.com/gogf/gf/internal/intlog"
	"github.com/gogf/gf/os/gfpool"
	"github.com/gogf/gf/os/gmlock"
	"github.com/gogf/gf/os/gtimer"
	"io"
	"os"
	"strings"
	"sync"
	"time"

	"github.com/gogf/gf/debug/gdebug"

	"github.com/gogf/gf/os/gfile"
	"github.com/gogf/gf/os/gtime"
	"github.com/gogf/gf/text/gregex"
	"github.com/gogf/gf/util/gconv"
)

// Logger is the struct for logging management.
type Logger struct {
	rmu    sync.Mutex      // Mutex for rotation feature.
	ctx    context.Context // Context for logging.
	init   *gtype.Bool     // Initialized.
	parent *Logger         // Parent logger, if it is not empty, it means the logger is used in chaining function.
	config Config          // Logger configuration.
}

const (
	defaultFileFormat = `{Y-m-d}.log`
	defaultFileFlags  = os.O_CREATE | os.O_WRONLY | os.O_APPEND
	defaultFilePerm   = os.FileMode(0666)
	defaultFileExpire = time.Minute
	pathFilterKey     = "/os/glog/glog"
)

const (
	F_ASYNC      = 1 << iota // Print logging content asynchronously。
	F_FILE_LONG              // Print full file name and line number: /a/b/c/d.go:23.
	F_FILE_SHORT             // Print final file name element and line number: d.go:23. overrides F_FILE_LONG.
	F_TIME_DATE              // Print the date in the local time zone: 2009-01-23.
	F_TIME_TIME              // Print the time in the local time zone: 01:23:23.
	F_TIME_MILLI             // Print the time with milliseconds in the local time zone: 01:23:23.675.
	F_CALLER_FN              // Print Caller function name and package: main.main
	F_TIME_STD   = F_TIME_DATE | F_TIME_MILLI
)

// New creates and returns a custom logger.
func New() *Logger {
	logger := &Logger{
		init:   gtype.NewBool(),
		config: DefaultConfig(),
	}
	return logger
}

// NewWithWriter creates and returns a custom logger with io.Writer.
func NewWithWriter(writer io.Writer) *Logger {
	l := New()
	l.SetWriter(writer)
	return l
}

// Clone returns a new logger, which is the clone the current logger.
// It's commonly used for chaining operations.
func (l *Logger) Clone() *Logger {
	logger := New()
	logger.ctx = l.ctx
	logger.config = l.config
	logger.parent = l
	return logger
}

// getFilePath returns the logging file path.
// The logging file name must have extension name of "log".
func (l *Logger) getFilePath(now time.Time) string {
	// Content containing "{}" in the file name is formatted using gtime.
	file, _ := gregex.ReplaceStringFunc(`{.+?}`, l.config.File, func(s string) string {
		return gtime.New(now).Format(strings.Trim(s, "{}"))
	})
	file = gfile.Join(l.config.Path, file)
	return file
}

// print prints <s> to defined writer, logging file or passed <std>.
func (l *Logger) print(std io.Writer, lead string, values ...interface{}) {
	// Lazy initialize for rotation feature.
	// It uses atomic reading operation to enhance the performance checking.
	// It here uses CAP for performance and concurrent safety.
	p := l
	if p.parent != nil {
		p = p.parent
	}
	if !p.init.Val() && p.init.Cas(false, true) {
		// It just initializes once for each logger.
		if p.config.RotateSize > 0 || p.config.RotateExpire > 0 {
			gtimer.AddOnce(p.config.RotateCheckInterval, p.rotateChecksTimely)
			intlog.Printf("logger rotation initialized: every %s", p.config.RotateCheckInterval.String())
		}
	}

	var (
		now    = time.Now()
		buffer = bytes.NewBuffer(nil)
	)
	if l.config.HeaderPrint {
		// Time.
		timeFormat := ""
		if l.config.Flags&F_TIME_DATE > 0 {
			timeFormat += "2006-01-02 "
		}
		if l.config.Flags&F_TIME_TIME > 0 {
			timeFormat += "15:04:05 "
		}
		if l.config.Flags&F_TIME_MILLI > 0 {
			timeFormat += "15:04:05.000 "
		}
		if len(timeFormat) > 0 {
			buffer.WriteString(now.Format(timeFormat))
		}
		// Lead string.
		if len(lead) > 0 {
			buffer.WriteString(lead)
			if len(values) > 0 {
				buffer.WriteByte(' ')
			}
		}
		// Caller path and Fn name.
		if l.config.Flags&(F_FILE_LONG|F_FILE_SHORT|F_CALLER_FN) > 0 {
			callerPath := ""
			callerFnName, path, line := gdebug.CallerWithFilter(pathFilterKey, l.config.StSkip)
			if l.config.Flags&F_CALLER_FN > 0 {
				buffer.WriteString(fmt.Sprintf(`[%s] `, callerFnName))
			}
			if l.config.Flags&F_FILE_LONG > 0 {
				callerPath = fmt.Sprintf(`%s:%d: `, path, line)
			}
			if l.config.Flags&F_FILE_SHORT > 0 {
				callerPath = fmt.Sprintf(`%s:%d: `, gfile.Basename(path), line)
			}
			buffer.WriteString(callerPath)

		}
		// Prefix.
		if len(l.config.Prefix) > 0 {
			buffer.WriteString(l.config.Prefix + " ")
		}
	}
	// Convert value to string.
	var (
		tempStr  = ""
		valueStr = ""
	)
	// Context values.
	if l.ctx != nil && len(l.config.CtxKeys) > 0 {
		ctxStr := ""
		for _, key := range l.config.CtxKeys {
			if v := l.ctx.Value(key); v != nil {
				if ctxStr != "" {
					ctxStr += ", "
				}
				ctxStr += fmt.Sprintf("%s: %+v", key, v)
			}
		}
		if ctxStr != "" {
			buffer.WriteString(fmt.Sprintf("{%s} ", ctxStr))
		}
	}
	for _, v := range values {
		if err, ok := v.(error); ok {
			tempStr = fmt.Sprintf("%+v", err)
		} else {
			tempStr = gconv.String(v)
		}
		if len(valueStr) > 0 {
			if valueStr[len(valueStr)-1] == '\n' {
				// Remove one blank line(\n\n).
				if tempStr[0] == '\n' {
					valueStr += tempStr[1:]
				} else {
					valueStr += tempStr
				}
			} else {
				valueStr += " " + tempStr
			}
		} else {
			valueStr = tempStr
		}
	}
	buffer.WriteString(valueStr + "\n")
	if l.config.Flags&F_ASYNC > 0 {
		err := asyncPool.Add(func() {
			l.printToWriter(now, std, buffer)
		})
		if err != nil {
			intlog.Error(err)
		}
	} else {
		l.printToWriter(now, std, buffer)
	}
}

// printToWriter writes buffer to writer.
func (l *Logger) printToWriter(now time.Time, std io.Writer, buffer *bytes.Buffer) {
	if l.config.Writer == nil {
		// Output content to disk file.
		if l.config.Path != "" {
			l.printToFile(now, buffer)
		}
		// Allow output to stdout?
		if l.config.StdoutPrint {
			if _, err := std.Write(buffer.Bytes()); err != nil {
				intlog.Error(err)
			}
		}
	} else {
		if _, err := l.config.Writer.Write(buffer.Bytes()); err != nil {
			// panic(err)
			intlog.Error(err)
		}
	}
}

// printToFile outputs logging content to disk file.
func (l *Logger) printToFile(now time.Time, buffer *bytes.Buffer) {
	var (
		logFilePath   = l.getFilePath(now)
		memoryLockKey = "glog.file.lock:" + logFilePath
	)
	gmlock.Lock(memoryLockKey)
	defer gmlock.Unlock(memoryLockKey)
	file := l.getFilePointer(logFilePath)
	// Rotation file size checks.
	if l.config.RotateSize > 0 {
		stat, err := file.Stat()
		if err != nil {
			file.Close()
			// panic(err)
			intlog.Error(err)
			return
		}
		if stat.Size() > l.config.RotateSize {
			l.rotateFileBySize(now)
			file = l.getFilePointer(logFilePath)
		}
	}
	if _, err := file.Write(buffer.Bytes()); err != nil {
		file.Close()
		// panic(err)
		intlog.Error(err)
		return
	}
	file.Close()
}

// getFilePointer retrieves and returns a file pointer from file pool.
func (l *Logger) getFilePointer(path string) *gfpool.File {
	file, err := gfpool.Open(
		path,
		defaultFileFlags,
		defaultFilePerm,
		defaultFileExpire,
	)
	if err != nil {
		// panic(err)
		intlog.Error(err)
	}
	return file
}

// printStd prints content <s> without stack.
func (l *Logger) printStd(lead string, value ...interface{}) {
	l.print(os.Stdout, lead, value...)
}

// printStd prints content <s> with stack check.
func (l *Logger) printErr(lead string, value ...interface{}) {
	if l.config.StStatus == 1 {
		if s := l.GetStack(); s != "" {
			value = append(value, "\nStack:\n"+s)
		}
	}
	// In matter of sequence, do not use stderr here, but use the same stdout.
	l.print(os.Stdout, lead, value...)
}

// format formats <values> using fmt.Sprintf.
func (l *Logger) format(format string, value ...interface{}) string {
	return fmt.Sprintf(format, value...)
}

// PrintStack prints the caller stack,
// the optional parameter <skip> specify the skipped stack offset from the end point.
func (l *Logger) PrintStack(skip ...int) {
	if s := l.GetStack(skip...); s != "" {
		l.Println("Stack:\n" + s)
	} else {
		l.Println()
	}
}

// GetStack returns the caller stack content,
// the optional parameter <skip> specify the skipped stack offset from the end point.
func (l *Logger) GetStack(skip ...int) string {
	stackSkip := l.config.StSkip
	if len(skip) > 0 {
		stackSkip += skip[0]
	}
	filters := []string{pathFilterKey}
	if l.config.StFilter != "" {
		filters = append(filters, l.config.StFilter)
	}
	return gdebug.StackWithFilters(filters, stackSkip)
}
<commit_msg>improve package glog for nil file pointer<commit_after>// Copyright 2017 gf Author(https://github.com/gogf/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://github.com/gogf/gf.

package glog

import (
	"bytes"
	"context"
	"fmt"
	"github.com/gogf/gf/container/gtype"
	"github.com/gogf/gf/internal/intlog"
	"github.com/gogf/gf/os/gfpool"
	"github.com/gogf/gf/os/gmlock"
	"github.com/gogf/gf/os/gtimer"
	"io"
	"os"
	"strings"
	"sync"
	"time"

	"github.com/gogf/gf/debug/gdebug"

	"github.com/gogf/gf/os/gfile"
	"github.com/gogf/gf/os/gtime"
	"github.com/gogf/gf/text/gregex"
	"github.com/gogf/gf/util/gconv"
)

// Logger is the struct for logging management.
type Logger struct {
	rmu    sync.Mutex      // Mutex for rotation feature.
	ctx    context.Context // Context for logging.
	init   *gtype.Bool     // Initialized.
	parent *Logger         // Parent logger, if it is not empty, it means the logger is used in chaining function.
	config Config          // Logger configuration.
}

const (
	defaultFileFormat = `{Y-m-d}.log`
	defaultFileFlags  = os.O_CREATE | os.O_WRONLY | os.O_APPEND
	defaultFilePerm   = os.FileMode(0666)
	defaultFileExpire = time.Minute
	pathFilterKey     = "/os/glog/glog"
)

const (
	F_ASYNC      = 1 << iota // Print logging content asynchronously。
	F_FILE_LONG              // Print full file name and line number: /a/b/c/d.go:23.
	F_FILE_SHORT             // Print final file name element and line number: d.go:23. overrides F_FILE_LONG.
	F_TIME_DATE              // Print the date in the local time zone: 2009-01-23.
	F_TIME_TIME              // Print the time in the local time zone: 01:23:23.
	F_TIME_MILLI             // Print the time with milliseconds in the local time zone: 01:23:23.675.
	F_CALLER_FN              // Print Caller function name and package: main.main
	F_TIME_STD   = F_TIME_DATE | F_TIME_MILLI
)

// New creates and returns a custom logger.
func New() *Logger {
	logger := &Logger{
		init:   gtype.NewBool(),
		config: DefaultConfig(),
	}
	return logger
}

// NewWithWriter creates and returns a custom logger with io.Writer.
func NewWithWriter(writer io.Writer) *Logger {
	l := New()
	l.SetWriter(writer)
	return l
}

// Clone returns a new logger, which is the clone the current logger.
// It's commonly used for chaining operations.
func (l *Logger) Clone() *Logger {
	logger := New()
	logger.ctx = l.ctx
	logger.config = l.config
	logger.parent = l
	return logger
}

// getFilePath returns the logging file path.
// The logging file name must have extension name of "log".
func (l *Logger) getFilePath(now time.Time) string {
	// Content containing "{}" in the file name is formatted using gtime.
	file, _ := gregex.ReplaceStringFunc(`{.+?}`, l.config.File, func(s string) string {
		return gtime.New(now).Format(strings.Trim(s, "{}"))
	})
	file = gfile.Join(l.config.Path, file)
	return file
}

// print prints <s> to defined writer, logging file or passed <std>.
func (l *Logger) print(std io.Writer, lead string, values ...interface{}) {
	// Lazy initialize for rotation feature.
	// It uses atomic reading operation to enhance the performance checking.
	// It here uses CAP for performance and concurrent safety.
	p := l
	if p.parent != nil {
		p = p.parent
	}
	if !p.init.Val() && p.init.Cas(false, true) {
		// It just initializes once for each logger.
		if p.config.RotateSize > 0 || p.config.RotateExpire > 0 {
			gtimer.AddOnce(p.config.RotateCheckInterval, p.rotateChecksTimely)
			intlog.Printf("logger rotation initialized: every %s", p.config.RotateCheckInterval.String())
		}
	}

	var (
		now    = time.Now()
		buffer = bytes.NewBuffer(nil)
	)
	if l.config.HeaderPrint {
		// Time.
		timeFormat := ""
		if l.config.Flags&F_TIME_DATE > 0 {
			timeFormat += "2006-01-02 "
		}
		if l.config.Flags&F_TIME_TIME > 0 {
			timeFormat += "15:04:05 "
		}
		if l.config.Flags&F_TIME_MILLI > 0 {
			timeFormat += "15:04:05.000 "
		}
		if len(timeFormat) > 0 {
			buffer.WriteString(now.Format(timeFormat))
		}
		// Lead string.
		if len(lead) > 0 {
			buffer.WriteString(lead)
			if len(values) > 0 {
				buffer.WriteByte(' ')
			}
		}
		// Caller path and Fn name.
		if l.config.Flags&(F_FILE_LONG|F_FILE_SHORT|F_CALLER_FN) > 0 {
			callerPath := ""
			callerFnName, path, line := gdebug.CallerWithFilter(pathFilterKey, l.config.StSkip)
			if l.config.Flags&F_CALLER_FN > 0 {
				buffer.WriteString(fmt.Sprintf(`[%s] `, callerFnName))
			}
			if l.config.Flags&F_FILE_LONG > 0 {
				callerPath = fmt.Sprintf(`%s:%d: `, path, line)
			}
			if l.config.Flags&F_FILE_SHORT > 0 {
				callerPath = fmt.Sprintf(`%s:%d: `, gfile.Basename(path), line)
			}
			buffer.WriteString(callerPath)

		}
		// Prefix.
		if len(l.config.Prefix) > 0 {
			buffer.WriteString(l.config.Prefix + " ")
		}
	}
	// Convert value to string.
	var (
		tempStr  = ""
		valueStr = ""
	)
	// Context values.
	if l.ctx != nil && len(l.config.CtxKeys) > 0 {
		ctxStr := ""
		for _, key := range l.config.CtxKeys {
			if v := l.ctx.Value(key); v != nil {
				if ctxStr != "" {
					ctxStr += ", "
				}
				ctxStr += fmt.Sprintf("%s: %+v", key, v)
			}
		}
		if ctxStr != "" {
			buffer.WriteString(fmt.Sprintf("{%s} ", ctxStr))
		}
	}
	for _, v := range values {
		if err, ok := v.(error); ok {
			tempStr = fmt.Sprintf("%+v", err)
		} else {
			tempStr = gconv.String(v)
		}
		if len(valueStr) > 0 {
			if valueStr[len(valueStr)-1] == '\n' {
				// Remove one blank line(\n\n).
				if tempStr[0] == '\n' {
					valueStr += tempStr[1:]
				} else {
					valueStr += tempStr
				}
			} else {
				valueStr += " " + tempStr
			}
		} else {
			valueStr = tempStr
		}
	}
	buffer.WriteString(valueStr + "\n")
	if l.config.Flags&F_ASYNC > 0 {
		err := asyncPool.Add(func() {
			l.printToWriter(now, std, buffer)
		})
		if err != nil {
			intlog.Error(err)
		}
	} else {
		l.printToWriter(now, std, buffer)
	}
}

// printToWriter writes buffer to writer.
func (l *Logger) printToWriter(now time.Time, std io.Writer, buffer *bytes.Buffer) {
	if l.config.Writer == nil {
		// Output content to disk file.
		if l.config.Path != "" {
			l.printToFile(now, buffer)
		}
		// Allow output to stdout?
		if l.config.StdoutPrint {
			if _, err := std.Write(buffer.Bytes()); err != nil {
				intlog.Error(err)
			}
		}
	} else {
		if _, err := l.config.Writer.Write(buffer.Bytes()); err != nil {
			// panic(err)
			intlog.Error(err)
		}
	}
}

// printToFile outputs logging content to disk file.
func (l *Logger) printToFile(now time.Time, buffer *bytes.Buffer) {
	var (
		logFilePath   = l.getFilePath(now)
		memoryLockKey = "glog.file.lock:" + logFilePath
	)
	gmlock.Lock(memoryLockKey)
	defer gmlock.Unlock(memoryLockKey)
	file := l.getFilePointer(logFilePath)
	if file == nil {
		return
	}
	// Rotation file size checks.
	if l.config.RotateSize > 0 {
		stat, err := file.Stat()
		if err != nil {
			file.Close()
			// panic(err)
			intlog.Error(err)
			return
		}
		if stat.Size() > l.config.RotateSize {
			l.rotateFileBySize(now)
			file = l.getFilePointer(logFilePath)
		}
	}
	if _, err := file.Write(buffer.Bytes()); err != nil {
		file.Close()
		// panic(err)
		intlog.Error(err)
		return
	}
	file.Close()
}

// getFilePointer retrieves and returns a file pointer from file pool.
func (l *Logger) getFilePointer(path string) *gfpool.File {
	file, err := gfpool.Open(
		path,
		defaultFileFlags,
		defaultFilePerm,
		defaultFileExpire,
	)
	if err != nil {
		// panic(err)
		intlog.Error(err)
	}
	return file
}

// printStd prints content <s> without stack.
func (l *Logger) printStd(lead string, value ...interface{}) {
	l.print(os.Stdout, lead, value...)
}

// printStd prints content <s> with stack check.
func (l *Logger) printErr(lead string, value ...interface{}) {
	if l.config.StStatus == 1 {
		if s := l.GetStack(); s != "" {
			value = append(value, "\nStack:\n"+s)
		}
	}
	// In matter of sequence, do not use stderr here, but use the same stdout.
	l.print(os.Stdout, lead, value...)
}

// format formats <values> using fmt.Sprintf.
func (l *Logger) format(format string, value ...interface{}) string {
	return fmt.Sprintf(format, value...)
}

// PrintStack prints the caller stack,
// the optional parameter <skip> specify the skipped stack offset from the end point.
func (l *Logger) PrintStack(skip ...int) {
	if s := l.GetStack(skip...); s != "" {
		l.Println("Stack:\n" + s)
	} else {
		l.Println()
	}
}

// GetStack returns the caller stack content,
// the optional parameter <skip> specify the skipped stack offset from the end point.
func (l *Logger) GetStack(skip ...int) string {
	stackSkip := l.config.StSkip
	if len(skip) > 0 {
		stackSkip += skip[0]
	}
	filters := []string{pathFilterKey}
	if l.config.StFilter != "" {
		filters = append(filters, l.config.StFilter)
	}
	return gdebug.StackWithFilters(filters, stackSkip)
}
<|endoftext|>


<commit_before>package conio

import "fmt"

type KeyFuncResult int

const (
	CONTINUE KeyFuncResult = iota
	ENTER    KeyFuncResult = iota
	ABORT    KeyFuncResult = iota
)

func KeyFuncPass(this *ReadLineBuffer) KeyFuncResult {
	return CONTINUE
}

func KeyFuncEnter(this *ReadLineBuffer) KeyFuncResult { // Ctrl-M
	return ENTER
}

func KeyFuncHead(this *ReadLineBuffer) KeyFuncResult { // Ctrl-A
	Backspace(this.GetWidthBetween(this.ViewStart, this.Cursor))
	this.Cursor = 0
	this.ViewStart = 0
	this.Repaint(0, 1)
	return CONTINUE
}

func KeyFuncBackword(this *ReadLineBuffer) KeyFuncResult { // Ctrl-B
	if this.Cursor <= 0 {
		return CONTINUE
	}
	this.Cursor--
	if this.Cursor < this.ViewStart {
		this.ViewStart--
		this.Repaint(this.Cursor, 1)
	} else {
		Backspace(getCharWidth(this.Buffer[this.Cursor]))
	}
	return CONTINUE
}

func KeyFuncTail(this *ReadLineBuffer) KeyFuncResult { // Ctrl-E
	allength := this.GetWidthBetween(this.ViewStart, this.Length)
	if allength < this.ViewWidth {
		for ; this.Cursor < this.Length; this.Cursor++ {
			PutRep(this.Buffer[this.Cursor], 1)
		}
	} else {
		PutRep('\a', 1)
		Backspace(this.GetWidthBetween(this.ViewStart, this.Cursor))
		this.ViewStart = this.Length - 1
		w := getCharWidth(this.Buffer[this.ViewStart])
		for {
			if this.ViewStart <= 0 {
				break
			}
			w_ := w + getCharWidth(this.Buffer[this.ViewStart-1])
			if w_ >= this.ViewWidth {
				break
			}
			w = w_
			this.ViewStart--
		}
		for this.Cursor = this.ViewStart; this.Cursor < this.Length; this.Cursor++ {
			PutRep(this.Buffer[this.Cursor], 1)
		}
	}
	return CONTINUE
}

func KeyFuncForward(this *ReadLineBuffer) KeyFuncResult { // Ctrl-F
	if this.Cursor >= this.Length {
		return CONTINUE
	}
	w := this.GetWidthBetween(this.ViewStart, this.Cursor+1)
	if w < this.ViewWidth {
		// No Scroll
		PutRep(this.Buffer[this.Cursor], 1)
	} else {
		// Right Scroll
		Backspace(this.GetWidthBetween(this.ViewStart, this.Cursor))
		if getCharWidth(this.Buffer[this.Cursor]) > getCharWidth(this.Buffer[this.ViewStart]) {
			this.ViewStart++
		}
		this.ViewStart++
		for i := this.ViewStart; i <= this.Cursor; i++ {
			PutRep(this.Buffer[i], 1)
		}
		PutRep(' ', 1)
		Backspace(1)
	}
	this.Cursor++
	return CONTINUE
}

func KeyFuncBackSpace(this *ReadLineBuffer) KeyFuncResult { // Backspace
	if this.Cursor > 0 {
		this.Cursor--
		delw := this.Delete(this.Cursor, 1)
		if this.Cursor >= this.ViewStart {
			Backspace(delw)
		} else {
			this.ViewStart = this.Cursor
		}
		this.Repaint(this.Cursor, delw)
	}
	return CONTINUE
}

func KeyFuncDelete(this *ReadLineBuffer) KeyFuncResult { // Del
	delw := this.Delete(this.Cursor, 1)
	this.Repaint(this.Cursor, delw)
	return CONTINUE
}

func KeyFuncDeleteOrAbort(this *ReadLineBuffer) KeyFuncResult { // Ctrl-D
	if this.Length > 0 {
		return KeyFuncDelete(this)
	} else {
		return ABORT
	}
}

func KeyFuncInsertSelf(this *ReadLineBuffer) KeyFuncResult {
	ch := this.Unicode
	if ch < 0x20 || !this.Insert(this.Cursor, []rune{ch}) {
		return CONTINUE
	}
	w := this.GetWidthBetween(this.ViewStart, this.Cursor)
	w1 := getCharWidth(ch)
	if w+w1 >= this.ViewWidth {
		// scroll left
		Backspace(w)
		if getCharWidth(this.Buffer[this.ViewStart]) < w1 {
			this.ViewStart++
		}
		this.ViewStart++
		for i := this.ViewStart; i <= this.Cursor; i++ {
			PutRep(this.Buffer[i], 1)
		}
		PutRep(' ', 1)
		Backspace(1)
	} else {
		this.Repaint(this.Cursor, -w1)
	}
	this.Cursor++
	return CONTINUE
}

func KeyFuncInsertReport(this *ReadLineBuffer) KeyFuncResult {
	insertAndRepaint(this, fmt.Sprintf("[%X]", this.Unicode))
	return CONTINUE
}

func insertAndRepaint(this *ReadLineBuffer, text string) {
	L := this.InsertString(this.Cursor, text)
	if L >= 0 {
		this.Repaint(this.Cursor, -L)
		this.Cursor += L
	}
}

func KeyFuncClearAfter(this *ReadLineBuffer) KeyFuncResult {
	w := this.GetWidthBetween(this.ViewStart, this.Cursor)
	i := this.Cursor
	bs := 0
	for i < this.Length && w < this.ViewWidth {
		w1 := getCharWidth(this.Buffer[i])
		PutRep(' ', w1)
		i++
		w += w1
		bs += w1
	}
	Backspace(bs)
	this.Length = this.Cursor
	return CONTINUE
}

func KeyFuncClear(this *ReadLineBuffer) KeyFuncResult {
	KeyFuncClearAfter(this)
	width := this.GetWidthBetween(this.ViewStart, this.Cursor)
	Backspace(width)
	PutRep(' ', width)
	Backspace(width)
	this.Length = 0
	this.Cursor = 0
	this.ViewStart = 0
	return CONTINUE
}

func KeyFuncClearBefore(this *ReadLineBuffer) KeyFuncResult {
	keta := this.Delete(0, this.Cursor)
	Backspace(keta)
	this.Cursor = 0
	this.Repaint(0, keta)
	return CONTINUE
}

func KeyFuncCLS(this *ReadLineBuffer) KeyFuncResult {
	Cls()
	this.RepaintAll()
	return CONTINUE
}

func KeyFuncPaste(this *ReadLineBuffer) KeyFuncResult {
	insertAndRepaint(this, GetClipboardString())
	return CONTINUE
}

var KeyMap = map[rune]func(*ReadLineBuffer) KeyFuncResult{
	'\r':         KeyFuncEnter,
	'\x01':       KeyFuncHead,
	'\x02':       KeyFuncBackword,
	'\x05':       KeyFuncTail,
	'\x06':       KeyFuncForward,
	'\b':         KeyFuncBackSpace,
	'\x04':       KeyFuncDeleteOrAbort,
	'\x7F':       KeyFuncDelete,
	('K' & 0x1F): KeyFuncClearAfter,
	'\x1B':       KeyFuncClear,
	('U' & 0x1F): KeyFuncClearBefore,
	('L' & 0x1F): KeyFuncCLS,
	('Y' & 0x1F): KeyFuncPaste,
}

// KeyCode from
// http://msdn.microsoft.com/ja-jp/library/windows/desktop/dd375731(v=vs.85).aspx
const (
	K_LEFT  = 0x25
	K_RIGHT = 0x27
	K_DEL   = 0x2E
	K_HOME  = 0x24
	K_END   = 0x23
	K_CTRL  = 0x11
	K_SHIFT = 0x10
	K_UP    = 0x26
	K_DOWN  = 0x28
)

var ZeroMap = map[uint16]func(*ReadLineBuffer) KeyFuncResult{
	K_LEFT:  KeyFuncBackword,
	K_RIGHT: KeyFuncForward,
	K_DEL:   KeyFuncDelete,
	K_HOME:  KeyFuncHead,
	K_END:   KeyFuncTail,
	K_CTRL:  KeyFuncPass,
	K_SHIFT: KeyFuncPass,
}

func ReadLine(prompt_ func()) (string, KeyFuncResult) {
	var this ReadLineBuffer
	this.Buffer = make([]rune, 20)
	this.Length = 0
	this.Cursor = 0
	this.ViewStart = 0
	this.ViewWidth = 60
	this.Prompt = prompt_
	if this.Prompt != nil {
		this.Prompt()
	}
	for {
		stdOut.Flush()
		shineCursor()
		this.Unicode, this.Keycode = GetKey()
		var f func(*ReadLineBuffer) KeyFuncResult
		var ok bool
		if this.Unicode != 0 {
			f, ok = KeyMap[this.Unicode]
			if !ok {
				//f = KeyFuncInsertReport
				f = KeyFuncInsertSelf
			}
		} else {
			f, ok = ZeroMap[this.Keycode]
			if !ok {
				f = KeyFuncPass
			}
		}
		rc := f(&this)
		if rc != CONTINUE {
			stdOut.WriteRune('\n')
			stdOut.Flush()
			return this.String(), rc
		}
	}
}
<commit_msg>ペーストする時に改行・タブを空白1文字にするようにした。<commit_after>package conio

import "strings"
import "fmt"

type KeyFuncResult int

const (
	CONTINUE KeyFuncResult = iota
	ENTER    KeyFuncResult = iota
	ABORT    KeyFuncResult = iota
)

func KeyFuncPass(this *ReadLineBuffer) KeyFuncResult {
	return CONTINUE
}

func KeyFuncEnter(this *ReadLineBuffer) KeyFuncResult { // Ctrl-M
	return ENTER
}

func KeyFuncHead(this *ReadLineBuffer) KeyFuncResult { // Ctrl-A
	Backspace(this.GetWidthBetween(this.ViewStart, this.Cursor))
	this.Cursor = 0
	this.ViewStart = 0
	this.Repaint(0, 1)
	return CONTINUE
}

func KeyFuncBackword(this *ReadLineBuffer) KeyFuncResult { // Ctrl-B
	if this.Cursor <= 0 {
		return CONTINUE
	}
	this.Cursor--
	if this.Cursor < this.ViewStart {
		this.ViewStart--
		this.Repaint(this.Cursor, 1)
	} else {
		Backspace(getCharWidth(this.Buffer[this.Cursor]))
	}
	return CONTINUE
}

func KeyFuncTail(this *ReadLineBuffer) KeyFuncResult { // Ctrl-E
	allength := this.GetWidthBetween(this.ViewStart, this.Length)
	if allength < this.ViewWidth {
		for ; this.Cursor < this.Length; this.Cursor++ {
			PutRep(this.Buffer[this.Cursor], 1)
		}
	} else {
		PutRep('\a', 1)
		Backspace(this.GetWidthBetween(this.ViewStart, this.Cursor))
		this.ViewStart = this.Length - 1
		w := getCharWidth(this.Buffer[this.ViewStart])
		for {
			if this.ViewStart <= 0 {
				break
			}
			w_ := w + getCharWidth(this.Buffer[this.ViewStart-1])
			if w_ >= this.ViewWidth {
				break
			}
			w = w_
			this.ViewStart--
		}
		for this.Cursor = this.ViewStart; this.Cursor < this.Length; this.Cursor++ {
			PutRep(this.Buffer[this.Cursor], 1)
		}
	}
	return CONTINUE
}

func KeyFuncForward(this *ReadLineBuffer) KeyFuncResult { // Ctrl-F
	if this.Cursor >= this.Length {
		return CONTINUE
	}
	w := this.GetWidthBetween(this.ViewStart, this.Cursor+1)
	if w < this.ViewWidth {
		// No Scroll
		PutRep(this.Buffer[this.Cursor], 1)
	} else {
		// Right Scroll
		Backspace(this.GetWidthBetween(this.ViewStart, this.Cursor))
		if getCharWidth(this.Buffer[this.Cursor]) > getCharWidth(this.Buffer[this.ViewStart]) {
			this.ViewStart++
		}
		this.ViewStart++
		for i := this.ViewStart; i <= this.Cursor; i++ {
			PutRep(this.Buffer[i], 1)
		}
		PutRep(' ', 1)
		Backspace(1)
	}
	this.Cursor++
	return CONTINUE
}

func KeyFuncBackSpace(this *ReadLineBuffer) KeyFuncResult { // Backspace
	if this.Cursor > 0 {
		this.Cursor--
		delw := this.Delete(this.Cursor, 1)
		if this.Cursor >= this.ViewStart {
			Backspace(delw)
		} else {
			this.ViewStart = this.Cursor
		}
		this.Repaint(this.Cursor, delw)
	}
	return CONTINUE
}

func KeyFuncDelete(this *ReadLineBuffer) KeyFuncResult { // Del
	delw := this.Delete(this.Cursor, 1)
	this.Repaint(this.Cursor, delw)
	return CONTINUE
}

func KeyFuncDeleteOrAbort(this *ReadLineBuffer) KeyFuncResult { // Ctrl-D
	if this.Length > 0 {
		return KeyFuncDelete(this)
	} else {
		return ABORT
	}
}

func KeyFuncInsertSelf(this *ReadLineBuffer) KeyFuncResult {
	ch := this.Unicode
	if ch < 0x20 || !this.Insert(this.Cursor, []rune{ch}) {
		return CONTINUE
	}
	w := this.GetWidthBetween(this.ViewStart, this.Cursor)
	w1 := getCharWidth(ch)
	if w+w1 >= this.ViewWidth {
		// scroll left
		Backspace(w)
		if getCharWidth(this.Buffer[this.ViewStart]) < w1 {
			this.ViewStart++
		}
		this.ViewStart++
		for i := this.ViewStart; i <= this.Cursor; i++ {
			PutRep(this.Buffer[i], 1)
		}
		PutRep(' ', 1)
		Backspace(1)
	} else {
		this.Repaint(this.Cursor, -w1)
	}
	this.Cursor++
	return CONTINUE
}

func KeyFuncInsertReport(this *ReadLineBuffer) KeyFuncResult {
	insertAndRepaint(this, fmt.Sprintf("[%X]", this.Unicode))
	return CONTINUE
}

func insertAndRepaint(this *ReadLineBuffer, text string) {
	L := this.InsertString(this.Cursor, text)
	if L >= 0 {
		this.Repaint(this.Cursor, -L)
		this.Cursor += L
	}
}

func KeyFuncClearAfter(this *ReadLineBuffer) KeyFuncResult {
	w := this.GetWidthBetween(this.ViewStart, this.Cursor)
	i := this.Cursor
	bs := 0
	for i < this.Length && w < this.ViewWidth {
		w1 := getCharWidth(this.Buffer[i])
		PutRep(' ', w1)
		i++
		w += w1
		bs += w1
	}
	Backspace(bs)
	this.Length = this.Cursor
	return CONTINUE
}

func KeyFuncClear(this *ReadLineBuffer) KeyFuncResult {
	KeyFuncClearAfter(this)
	width := this.GetWidthBetween(this.ViewStart, this.Cursor)
	Backspace(width)
	PutRep(' ', width)
	Backspace(width)
	this.Length = 0
	this.Cursor = 0
	this.ViewStart = 0
	return CONTINUE
}

func KeyFuncClearBefore(this *ReadLineBuffer) KeyFuncResult {
	keta := this.Delete(0, this.Cursor)
	Backspace(keta)
	this.Cursor = 0
	this.Repaint(0, keta)
	return CONTINUE
}

func KeyFuncCLS(this *ReadLineBuffer) KeyFuncResult {
	Cls()
	this.RepaintAll()
	return CONTINUE
}

func KeyFuncPaste(this *ReadLineBuffer) KeyFuncResult {
	insertAndRepaint(this,
		strings.Replace(
			strings.Replace(
				strings.Replace(GetClipboardString(), "\n", " ",-1),
				"\r", "",-1),
			"\t", " ",-1))
	return CONTINUE
}

var KeyMap = map[rune]func(*ReadLineBuffer) KeyFuncResult{
	'\r':         KeyFuncEnter,
	'\x01':       KeyFuncHead,
	'\x02':       KeyFuncBackword,
	'\x05':       KeyFuncTail,
	'\x06':       KeyFuncForward,
	'\b':         KeyFuncBackSpace,
	'\x04':       KeyFuncDeleteOrAbort,
	'\x7F':       KeyFuncDelete,
	('K' & 0x1F): KeyFuncClearAfter,
	'\x1B':       KeyFuncClear,
	('U' & 0x1F): KeyFuncClearBefore,
	('L' & 0x1F): KeyFuncCLS,
	('Y' & 0x1F): KeyFuncPaste,
}

// KeyCode from
// http://msdn.microsoft.com/ja-jp/library/windows/desktop/dd375731(v=vs.85).aspx
const (
	K_LEFT  = 0x25
	K_RIGHT = 0x27
	K_DEL   = 0x2E
	K_HOME  = 0x24
	K_END   = 0x23
	K_CTRL  = 0x11
	K_SHIFT = 0x10
	K_UP    = 0x26
	K_DOWN  = 0x28
)

var ZeroMap = map[uint16]func(*ReadLineBuffer) KeyFuncResult{
	K_LEFT:  KeyFuncBackword,
	K_RIGHT: KeyFuncForward,
	K_DEL:   KeyFuncDelete,
	K_HOME:  KeyFuncHead,
	K_END:   KeyFuncTail,
	K_CTRL:  KeyFuncPass,
	K_SHIFT: KeyFuncPass,
}

func ReadLine(prompt_ func()) (string, KeyFuncResult) {
	var this ReadLineBuffer
	this.Buffer = make([]rune, 20)
	this.Length = 0
	this.Cursor = 0
	this.ViewStart = 0
	this.ViewWidth = 60
	this.Prompt = prompt_
	if this.Prompt != nil {
		this.Prompt()
	}
	for {
		stdOut.Flush()
		shineCursor()
		this.Unicode, this.Keycode = GetKey()
		var f func(*ReadLineBuffer) KeyFuncResult
		var ok bool
		if this.Unicode != 0 {
			f, ok = KeyMap[this.Unicode]
			if !ok {
				//f = KeyFuncInsertReport
				f = KeyFuncInsertSelf
			}
		} else {
			f, ok = ZeroMap[this.Keycode]
			if !ok {
				f = KeyFuncPass
			}
		}
		rc := f(&this)
		if rc != CONTINUE {
			stdOut.WriteRune('\n')
			stdOut.Flush()
			return this.String(), rc
		}
	}
}
<|endoftext|>


<commit_before>package conn

import (
	"fmt"
	"net"
	"sync"
	"testing"
	"time"
)

func TestConnect(t *testing.T) {
	_, err := Dial("www.google.ca:80")
	if err != nil {
		fmt.Println(err.Error())
		t.Fail()
	}
}

func TestListen(t *testing.T) {
	wg := sync.WaitGroup{}
	wg.Add(6)

	handler := func(c net.Conn) {
		defer c.Close()
		buf := make([]byte, 1)
		n, err := c.Read(buf)
		if err != nil {
			t.Fail()
		}
		if n != 1 {
			t.Fail()
		}
		wg.Done()
	}

	go Listen(":8080", handler, &wg)
	// Sleep to guarantee that our listener is ready when we start making connections
	time.Sleep(time.Millisecond)

	for i := 0; i < 5; i++ {
		go func() {
			c, err := Dial(":8080")
			if err != nil {
				t.Fail()
				return
			}
			c.Write([]byte{byte(i)})
		}()
	}

	wg.Wait()
	fmt.Println("ending")
}
<commit_msg>Fix waitgroup usage in conn_test.go<commit_after>package conn

import (
	"fmt"
	"net"
	"sync"
	"testing"
)

func TestConnect(t *testing.T) {
	_, err := Dial("www.google.ca:80")
	if err != nil {
		fmt.Println(err.Error())
		t.Fail()
	}
}

func TestListen(t *testing.T) {
	wg := sync.WaitGroup{}
	wg.Add(1)

	handler := func(c net.Conn) {
		defer c.Close()
		buf := make([]byte, 1)
		n, err := c.Read(buf)
		if err != nil {
			t.Fail()
		}
		if n != 1 {
			t.Fail()
		}
		wg.Done()
	}

	go Listen(":8080", handler, &wg)
	wg.Wait()
	wg.Add(5)

	for i := 0; i < 5; i++ {
		go func() {
			c, err := Dial(":8080")
			if err != nil {
				t.Fail()
				return
			}
			c.Write([]byte{byte(i)})
		}()
	}

	wg.Wait()
	fmt.Println("ending")
}
<|endoftext|>


<commit_before>package reloader

import (
	"log"

	"github.com/tripism/temple"
	"gopkg.in/fsnotify.v1"
)

type Reloader struct {
	t        *temple.Temple
	watcher  *fsnotify.Watcher
	stopchan chan struct{}
}

func New(t *temple.Temple) (*Reloader, error) {
	r := &Reloader{t: t}
	var err error
	if r.watcher, err = fsnotify.NewWatcher(); err != nil {
		return nil, err
	}
	go func() {
		for {
			select {
			case event := <-r.watcher.Events:
				if event.Op&fsnotify.Write == fsnotify.Write {
					log.Println("temple: changed:", event.Name, "(reloading)")
					r.t.Reload()
				}
			case err := <-r.watcher.Errors:
				log.Println("temple: reloader failed:", err)
			}
		}
	}()

	for _, file := range t.Files() {
		log.Println("temple.reloader: adding", file)
		if err := r.watcher.Add(file); err != nil {
			r.watcher.Close()
			return nil, err
		}
	}

	return r, nil
}

func (r *Reloader) Close() error {
	return r.watcher.Close()
}
<commit_msg>added comments<commit_after>package reloader

import (
	"log"

	"github.com/tripism/temple"
	"gopkg.in/fsnotify.v1"
)

// Reloader watches for changing files and calls Reload
// on a Temple.
type Reloader struct {
	t        *temple.Temple
	watcher  *fsnotify.Watcher
	stopchan chan struct{}
}

// New creates a new Reloader.
func New(t *temple.Temple) (*Reloader, error) {
	r := &Reloader{t: t}
	var err error
	if r.watcher, err = fsnotify.NewWatcher(); err != nil {
		return nil, err
	}
	go func() {
		for {
			select {
			case event := <-r.watcher.Events:
				if event.Op&fsnotify.Write == fsnotify.Write {
					log.Println("temple: changed:", event.Name, "(reloading)")
					r.t.Reload()
				}
			case err := <-r.watcher.Errors:
				log.Println("temple: reloader failed:", err)
			}
		}
	}()

	for _, file := range t.Files() {
		log.Println("temple.reloader: watching", file)
		if err := r.watcher.Add(file); err != nil {
			r.watcher.Close()
			return nil, err
		}
	}

	return r, nil
}

// Close stops watching the files and cleans things up.
func (r *Reloader) Close() error {
	return r.watcher.Close()
}
<|endoftext|>


<commit_before>// Package handlers provides HTTP request handlers.
package handlers

import (
	"net/http"

	"github.com/thomaso-mirodin/shawty/storages"
)

func EncodeHandler(storage storages.Storage) http.Handler {
	handleFunc := func(w http.ResponseWriter, r *http.Request) {
		if url := r.PostFormValue("url"); url != "" {
			w.Write([]byte(storage.Save(url)))
		}
	}

	return http.HandlerFunc(handleFunc)
}

func DecodeHandler(storage storages.Storage) http.Handler {
	handleFunc := func(w http.ResponseWriter, r *http.Request) {
		code := r.URL.Path[len("/dec/"):]

		url, err := storage.Load(code)
		if err != nil {
			w.WriteHeader(http.StatusNotFound)
			w.Write([]byte("URL Not Found. Error: " + err.Error() + "\n"))
			return
		}

		w.Write([]byte(url))
	}

	return http.HandlerFunc(handleFunc)
}

func RedirectHandler(storage storages.Storage) http.Handler {
	handleFunc := func(w http.ResponseWriter, r *http.Request) {
		code := r.URL.Path[len("/red/"):]

		url, err := storage.Load(code)
		if err != nil {
			w.WriteHeader(http.StatusNotFound)
			w.Write([]byte("URL Not Found. Error: " + err.Error() + "\n"))
			return
		}

		http.Redirect(w, r, string(url), 301)
	}

	return http.HandlerFunc(handleFunc)
}
<commit_msg>Fix Save handler<commit_after>// Package handlers provides HTTP request handlers.
package handlers

import (
	"net/http"

	"github.com/thomaso-mirodin/shawty/storages"
)

func EncodeHandler(storage storages.Storage) http.Handler {
	handleFunc := func(w http.ResponseWriter, r *http.Request) {
		if url := r.PostFormValue("url"); url != "" {
			code, err := storage.Save(url)
			if err != nil {
				http.Error(w, err, 500)
				return
			}
			w.Write([]byte(code))
		}
	}

	return http.HandlerFunc(handleFunc)
}

func DecodeHandler(storage storages.Storage) http.Handler {
	handleFunc := func(w http.ResponseWriter, r *http.Request) {
		code := r.URL.Path[len("/dec/"):]

		url, err := storage.Load(code)
		if err != nil {
			w.WriteHeader(http.StatusNotFound)
			w.Write([]byte("URL Not Found. Error: " + err.Error() + "\n"))
			return
		}

		w.Write([]byte(url))
	}

	return http.HandlerFunc(handleFunc)
}

func RedirectHandler(storage storages.Storage) http.Handler {
	handleFunc := func(w http.ResponseWriter, r *http.Request) {
		code := r.URL.Path[len("/red/"):]

		url, err := storage.Load(code)
		if err != nil {
			w.WriteHeader(http.StatusNotFound)
			w.Write([]byte("URL Not Found. Error: " + err.Error() + "\n"))
			return
		}

		http.Redirect(w, r, string(url), 301)
	}

	return http.HandlerFunc(handleFunc)
}
<|endoftext|>


<commit_before>package handlers

import (
	"net/http"
	"strconv"

	log "github.com/Sirupsen/logrus"
	"github.com/pressly/chi"
	strava "github.com/strava/go.strava"

	"github.com/jrzimmerman/bestrida-server-go/models"
)

// GetSegmentByID returns segment by ID from the database
func GetSegmentByID(w http.ResponseWriter, r *http.Request) {
	res := New(w)

	id := chi.URLParam(r, "id")
	numID, err := strconv.Atoi(id)
	if err != nil {
		log.WithField("id", numID).Debug("unable to convert ID param")
		res.Render(500, err)
		return
	}

	// log segment ID
	log.WithField("id", numID).Info("looking for segment by ID")

	segment, err := models.GetSegmentByID(numID)
	if err != nil {
		log.WithField("id", numID).Debug("unable to get segment by ID")
		res.Render(500, err)
		return
	}

	res.Render(200, segment)
}

// GetSegmentByIDFromStrava returns the strava segment with the specified ID
func GetSegmentByIDFromStrava(w http.ResponseWriter, r *http.Request) {
	res := New(w)

	id := chi.URLParam(r, "id")

	numID, err := strconv.ParseInt(id, 10, 64)
	if err != nil {
		log.WithField("ID", numID).Error("unable to convert segment ID param")
		res.Render(500, "unable to convert segment ID param")
		return
	}

	// use our access token to grab generic segment info
	client := strava.NewClient(accessToken)

	log.Infof("Fetching segment %v info...", id)
	segment, err := strava.NewSegmentsService(client).Get(numID).Do()
	if err != nil {
		res.Render(500, "Unable to retrieve segment info")
		return
	}

	res.Render(200, segment)
}
<commit_msg>update segment models to save and update<commit_after>package handlers

import (
	"net/http"
	"strconv"

	log "github.com/Sirupsen/logrus"
	"github.com/pressly/chi"
	strava "github.com/strava/go.strava"

	"github.com/jrzimmerman/bestrida-server-go/models"
)

// GetSegmentByID returns segment by ID from the database
func GetSegmentByID(w http.ResponseWriter, r *http.Request) {
	res := New(w)

	id := chi.URLParam(r, "id")
	numID, err := strconv.ParseInt(id, 10, 64)
	if err != nil {
		log.WithField("id", numID).Debug("unable to convert ID param")
		res.Render(500, err)
		return
	}

	// log segment ID
	log.WithField("id", numID).Info("looking for segment by ID")

	segment, err := models.GetSegmentByID(numID)
	if err != nil {
		log.WithField("id", numID).Debug("unable to get segment by ID")
		res.Render(500, err)
		return
	}

	res.Render(200, segment)
}

// GetSegmentByIDFromStrava returns the strava segment with the specified ID
func GetSegmentByIDFromStrava(w http.ResponseWriter, r *http.Request) {
	res := New(w)

	id := chi.URLParam(r, "id")

	numID, err := strconv.ParseInt(id, 10, 64)
	if err != nil {
		log.WithField("ID", numID).Error("unable to convert segment ID param")
		res.Render(500, "unable to convert segment ID param")
		return
	}

	// use our access token to grab generic segment info
	client := strava.NewClient(accessToken)

	log.Infof("Fetching segment %v info from strava...", id)
	segment, err := strava.NewSegmentsService(client).Get(numID).Do()
	if err != nil {
		res.Render(500, "Unable to retrieve segment info")
		return
	}
	log.Infof("rate limit percent: %v", strava.RateLimiting.FractionReached()*100)
	log.Infof("segment %v retrieved from strava", segment.Id)
	res.Render(200, segment)

	// store segment in background after render
	s, err := models.GetSegmentByID(segment.Id)
	if err != nil {
		models.SaveSegment(segment)
	} else {
		s.UpdateSegment(segment)
	}
}
<|endoftext|>


<commit_before>package sitemap

import (
	"bytes"
	"testing"
	"time"
)

func TestSiteMap(t *testing.T) {
	tests := map[string]struct {
		urlset URLSet
		xml    []byte
	}{
		"basic": {
			urlset: URLSet{
				URLs: []URL{
					{
						Loc:        "http://www.example.com/",
						LastMod:    time.Date(2013, time.September, 9, 11, 22, 33, 0, time.UTC),
						ChangeFreq: Daily,
						Priority:   0.7,
					},
				},
			},
			xml: []byte(`
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>http://www.example.com/</loc>
    <lastmod>2013-09-09T11:22:33Z</lastmod>
    <changefreq>daily</changefreq>
    <priority>0.7</priority>
  </url>
</urlset>`),
		},
	}

	for label, test := range tests {
		xml, err := Marshal(&test.urlset)
		if err != nil {
			t.Errorf("%s: Marshal: %s", label, err)
			continue
		}

		// Trim whitespace from the expected XML so we can compare it to the
		// actual XML output.
		test.xml = bytes.Replace(bytes.Replace(test.xml, []byte("  "), nil, -1), []byte("\n"), nil, -1)

		if !bytes.Equal(test.xml, xml) {
			t.Errorf("%s: want XML %q, got %q", label, test.xml, xml)
		}
	}
}
<commit_msg>Rename TestSiteMap -> TestMarshal for consistency with its test subject<commit_after>package sitemap

import (
	"bytes"
	"testing"
	"time"
)

func TestMarshal(t *testing.T) {
	tests := map[string]struct {
		urlset URLSet
		xml    []byte
	}{
		"basic": {
			urlset: URLSet{
				URLs: []URL{
					{
						Loc:        "http://www.example.com/",
						LastMod:    time.Date(2013, time.September, 9, 11, 22, 33, 0, time.UTC),
						ChangeFreq: Daily,
						Priority:   0.7,
					},
				},
			},
			xml: []byte(`
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>http://www.example.com/</loc>
    <lastmod>2013-09-09T11:22:33Z</lastmod>
    <changefreq>daily</changefreq>
    <priority>0.7</priority>
  </url>
</urlset>`),
		},
	}

	for label, test := range tests {
		xml, err := Marshal(&test.urlset)
		if err != nil {
			t.Errorf("%s: Marshal: %s", label, err)
			continue
		}

		// Trim whitespace from the expected XML so we can compare it to the
		// actual XML output.
		test.xml = bytes.Replace(bytes.Replace(test.xml, []byte("  "), nil, -1), []byte("\n"), nil, -1)

		if !bytes.Equal(test.xml, xml) {
			t.Errorf("%s: want XML %q, got %q", label, test.xml, xml)
		}
	}
}
<|endoftext|>


<commit_before>package consul

import (
	"fmt"
	"github.com/Comcast/webpa-common/service/monitor"
	"github.com/Comcast/webpa-common/xresolver"
	"github.com/stretchr/testify/assert"
	"io/ioutil"
	"net"
	"net/http"
	"net/http/httptest"
	"testing"
)

func TestConsulWatcher(t *testing.T) {
	assert := assert.New(t)

	customhost := "custom.host.com"
	customport := "8080"
	service := "custom"
	expectedBody := "Hello World\n"

	//customInstance := "custom.host-A.com"

	serverA := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, "a"+expectedBody)
	}))
	defer serverA.Close()

	serverB := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, "b"+expectedBody)
	}))
	defer serverB.Close()

	watcher := NewConsulWatcher(&Options{
		Watch: map[string]string{customhost: service},
	})

	watcher.MonitorEvent(monitor.Event{
		Key:       service + "[tag tagA]" + "{passingOnly=true}",
		Instances: []string{serverA.URL, serverB.URL},
	})

	client := &http.Client{
		Transport: &http.Transport{
			DialContext:       xresolver.NewResolver(nil, watcher).DialContext,
			DisableKeepAlives: true,
		},
	}

	req, err := http.NewRequest("GET", "http://"+net.JoinHostPort(customhost, customport), nil)
	assert.NoError(err)

	res, err := client.Do(req)
	if assert.NoError(err) {

		body, err := ioutil.ReadAll(res.Body)
		res.Body.Close()
		assert.NoError(err)

		assert.Equal(200, res.StatusCode)
		assert.Equal("a"+expectedBody, string(body))
	}

	req, err = http.NewRequest("GET", "http://"+net.JoinHostPort(customhost, customport), nil)
	assert.NoError(err)

	res, err = client.Do(req)
	if assert.NoError(err) {

		body, err := ioutil.ReadAll(res.Body)
		res.Body.Close()
		assert.NoError(err)

		assert.Equal(200, res.StatusCode)
		assert.Equal("b"+expectedBody, string(body))
	}
}
<commit_msg>added test notes<commit_after>package consul

import (
	"fmt"
	"github.com/Comcast/webpa-common/service/monitor"
	"github.com/Comcast/webpa-common/xresolver"
	"github.com/stretchr/testify/assert"
	"io/ioutil"
	"net"
	"net/http"
	"net/http/httptest"
	"testing"
)

func TestConsulWatcher(t *testing.T) {
	assert := assert.New(t)

	customhost := "custom.host.com"
	customport := "8080"
	service := "custom"
	expectedBody := "Hello World\n"

	//customInstance := "custom.host-A.com"

	serverA := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, "a"+expectedBody)
	}))
	defer serverA.Close()

	serverB := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, "b"+expectedBody)
	}))
	defer serverB.Close()

	watcher := NewConsulWatcher(&Options{
		Watch: map[string]string{customhost: service},
	})

	// note: MonitorEvent is Listen interface in the monitor package
	watcher.MonitorEvent(monitor.Event{
		Key:       service + "[tag tagA]" + "{passingOnly=true}",
		Instances: []string{serverA.URL, serverB.URL},
	})

	client := &http.Client{
		Transport: &http.Transport{
			DialContext: xresolver.NewResolver(nil, watcher).DialContext,
			// note: DisableKeepAlives is required so when we do the request again we don't reuse the same connection.
			DisableKeepAlives: true,
		},
	}

	req, err := http.NewRequest("GET", "http://"+net.JoinHostPort(customhost, customport), nil)
	assert.NoError(err)

	res, err := client.Do(req)
	if assert.NoError(err) {

		body, err := ioutil.ReadAll(res.Body)
		res.Body.Close()
		assert.NoError(err)

		assert.Equal(200, res.StatusCode)
		assert.Equal("a"+expectedBody, string(body))
	}

	req, err = http.NewRequest("GET", "http://"+net.JoinHostPort(customhost, customport), nil)
	assert.NoError(err)

	res, err = client.Do(req)
	if assert.NoError(err) {

		body, err := ioutil.ReadAll(res.Body)
		res.Body.Close()
		assert.NoError(err)

		assert.Equal(200, res.StatusCode)
		assert.Equal("b"+expectedBody, string(body))
	}
}
<|endoftext|>


<commit_before>package pushclient

import (
	"code.google.com/p/go-uuid/uuid"
	"code.google.com/p/go.net/websocket"
	"crypto/tls"
	"fmt"
	"log"
)

type Client struct {
	Ws         *websocket.Conn
	Uaid       string
	ChannelIDs []string
	handler    PushHandler
}

func NewClient(server string, port int, secure bool, handler PushHandler) (c *Client, err error) {
	var url, origin string
	if secure {
		origin = fmt.Sprintf("https://%s/", server)
		url = fmt.Sprintf("wss://%s:%d/", server, port)
	} else {
		origin = fmt.Sprintf("http://%s/", server)
		url = fmt.Sprintf("ws://%s:%d/", server, port)
	}

	config, err := websocket.NewConfig(url, origin)
	if err != nil {
		return
	}
	if secure {
		config.TlsConfig = &tls.Config{InsecureSkipVerify: true}
	}
	ws, err := websocket.DialConfig(config)
	if err != nil {
		return
	}

	c = &Client{Ws: ws, Uaid: "", ChannelIDs: make([]string, 0)}
	c.handler = handler
	err = c.handshake()
	if err != nil {
		return
	}
	return
}

func (c *Client) Run() {
	for {
		resp, err := c.Receive()
		if err != nil {
			return
		}
		switch resp["messageType"].(string) {
		case "register":
			go c.handleRegister(resp)
		case "notification":
			go c.handleNotification(resp)
		}
	}
}

func (c *Client) handleRegister(resp Response) {
	register := &RegisterResponse{
		ChannelID:    resp["channelID"].(string),
		Status:       int(resp["status"].(float64)),
		PushEndpoint: resp["pushEndpoint"].(string),
	}
	c.handler.RegisterHandler(register)
}

func (c *Client) handleNotification(resp Response) {
	n := &Notification{}
	for _, up := range resp["updates"].([]interface{}) {
		update := up.(map[string]interface{})
		u := &Update{ChannelID: update["channelID"].(string),
			Version: int(update["version"].(float64))}
		n.Updates = append(n.Updates, u)
	}
	c.handler.NotificationHandler(n)
	c.Send(&AckMessage{MessageType: "ack", Updates: n.Updates})
}

func (c *Client) Send(msg interface{}) (err error) {
	log.Println("client.send:", msg)
	err = websocket.JSON.Send(c.Ws, msg)
	return
}

func (c *Client) Receive() (resp Response, err error) {
	err = websocket.JSON.Receive(c.Ws, &resp)
	log.Println("client.recv:", resp)
	return
}

func (c *Client) handshake() (err error) {
	msg := HandshakeMessage{MessageType: "hello", Uaid: c.Uaid, ChannelIDs: c.ChannelIDs}
	err = c.Send(msg)
	if err != nil {
		return
	}

	resp, err := c.Receive()
	c.Uaid = resp["uaid"].(string)
	return
}

func (c *Client) Register() (err error) {
	msg := RegisterMessage{MessageType: "register", ChannelID: uuid.New()}
	err = c.Send(msg)
	return
}
<commit_msg>Add DEBUG env.<commit_after>package pushclient

import (
	"code.google.com/p/go-uuid/uuid"
	"code.google.com/p/go.net/websocket"
	"crypto/tls"
	"fmt"
	"log"
	"os"
)

type Client struct {
	Ws         *websocket.Conn
	Uaid       string
	ChannelIDs []string
	handler    PushHandler
}

func debug(args ...interface{}) {
	d := os.Getenv("DEBUG")
	if d == "1" {
		log.Println(args...)
	}
}

func NewClient(server string, port int, secure bool, handler PushHandler) (c *Client, err error) {
	var url, origin string
	if secure {
		origin = fmt.Sprintf("https://%s/", server)
		url = fmt.Sprintf("wss://%s:%d/", server, port)
	} else {
		origin = fmt.Sprintf("http://%s/", server)
		url = fmt.Sprintf("ws://%s:%d/", server, port)
	}

	config, err := websocket.NewConfig(url, origin)
	if err != nil {
		return
	}
	if secure {
		config.TlsConfig = &tls.Config{InsecureSkipVerify: true}
	}
	ws, err := websocket.DialConfig(config)
	if err != nil {
		return
	}

	c = &Client{Ws: ws, Uaid: "", ChannelIDs: make([]string, 0)}
	c.handler = handler
	err = c.handshake()
	if err != nil {
		return
	}
	return
}

func (c *Client) Run() {
	for {
		resp, err := c.Receive()
		if err != nil {
			return
		}
		switch resp["messageType"].(string) {
		case "register":
			go c.handleRegister(resp)
		case "notification":
			go c.handleNotification(resp)
		}
	}
}

func (c *Client) handleRegister(resp Response) {
	register := &RegisterResponse{
		ChannelID:    resp["channelID"].(string),
		Status:       int(resp["status"].(float64)),
		PushEndpoint: resp["pushEndpoint"].(string),
	}
	c.handler.RegisterHandler(register)
}

func (c *Client) handleNotification(resp Response) {
	n := &Notification{}
	for _, up := range resp["updates"].([]interface{}) {
		update := up.(map[string]interface{})
		u := &Update{ChannelID: update["channelID"].(string),
			Version: int(update["version"].(float64))}
		n.Updates = append(n.Updates, u)
	}
	c.handler.NotificationHandler(n)
	c.Send(&AckMessage{MessageType: "ack", Updates: n.Updates})
}

func (c *Client) Send(msg interface{}) (err error) {
	debug("client.send:", msg)
	err = websocket.JSON.Send(c.Ws, msg)
	return
}

func (c *Client) Receive() (resp Response, err error) {
	err = websocket.JSON.Receive(c.Ws, &resp)
	debug("client.recv:", resp)
	return
}

func (c *Client) handshake() (err error) {
	msg := HandshakeMessage{MessageType: "hello", Uaid: c.Uaid, ChannelIDs: c.ChannelIDs}
	err = c.Send(msg)
	if err != nil {
		return
	}

	resp, err := c.Receive()
	c.Uaid = resp["uaid"].(string)
	return
}

func (c *Client) Register() (err error) {
	msg := RegisterMessage{MessageType: "register", ChannelID: uuid.New()}
	err = c.Send(msg)
	return
}
<|endoftext|>


<commit_before>package helpers

import (
	. "seeme/models"
  "fmt"

  "net/http"

	"database/sql"
  _ "github.com/go-sql-driver/mysql"
)

var db *sql.DB

func InitMySQLConnection() {
  db, _ = sql.Open("mysql", "root:#54nFr4nc15c0@/seeme_db")
}

func VerifyMySQLConnection(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
  if err := db.Ping(); err != nil {
    http.Error(w, err.Error(), http.StatusInternalServerError)
  }
  next(w, r)
}

func GetDatabaseInstance() (*sql.DB) {
	return db
}

func InsertNewUser(user User) (error) {
	_, err := db.Exec("INSERT INTO users (first_name, last_name, role, username, secret, discoverable, network_id) VALUES (?, ?, ?, ?, ?, ?, ?)",
                  user.FirstName, user.LastName, user.Role, user.Username,
                  user.Secret, user.Discoverable, nil)
	return err
}

func GetUserFromDB(username string) (User, error) {
  var user User
  row := db.QueryRow("select * from users where username = ?", username)
  err := row.Scan(&user.FirstName, &user.LastName, &user.Role, &user.Username, &user.Secret, &user.Discoverable, &user.Network)

  return user, err
}

func GetDiscoverableUsersFromDB(w http.ResponseWriter) ([]User) {
  var userList []User
  var user User

  rows, err := db.Query("select * from users where discoverable = ?", 1)
  if err != nil {
    http.Error(w, err.Error(), http.StatusInternalServerError)
  }
  defer rows.Close()
  for rows.Next() {
    if err = rows.Scan(&user.FirstName, &user.LastName, &user.Role, &user.Username,
                        &user.Secret, &user.Discoverable, &user.Network); err != nil {
      http.Error(w, err.Error(), http.StatusInternalServerError)
    } else {
      userList = append(userList, user)
    }
  }
  if err = rows.Err(); err != nil {
    http.Error(w, err.Error(), http.StatusInternalServerError)
  }

  return userList
}

func GetLocalUsersForNetwork(w http.ResponseWriter, r *http.Request) ([]User) {
  var networkId string
  row := db.QueryRow("select network_id from networks where network_id = ?", r.FormValue("networkId"))
  if err := row.Scan(&networkId); err != nil {
    http.Error(w, err.Error(), http.StatusInternalServerError)
  }
  fmt.Println(networkId)
  if networkId == "" {
    if err := insertNewNetwork(r); err != nil {
      http.Error(w, err.Error(), http.StatusInternalServerError)
    }
  }

  if err := updateUserNetwork(r); err != nil {
    http.Error(w, err.Error(), http.StatusInternalServerError)
  }
  return getExistingUsersForNetwork(w, r)
}

func insertNewNetwork(r *http.Request) (error) {
  _, err := db.Exec("INSERT INTO networks (network_id, ssid) VALUES (?, ?)",
                  r.FormValue("networkId"), r.FormValue("ssid"))
  return err
}

func updateUserNetwork(r *http.Request) (error) {
  _, err := db.Exec("UPDATE users SET network_id=? WHERE username= ?",
                  r.FormValue("networkId"), r.FormValue("username"))
  return err
}

func getExistingUsersForNetwork(w http.ResponseWriter, r *http.Request) ([]User) {
  var userList []User
  var user User

  rows, err := db.Query("SELECT first_name, last_name, role, username, secret, discoverable, ssid FROM users INNER JOIN networks USING (network_id) WHERE discoverable = ? AND network_id=? AND !(username = ?)",
    1, r.FormValue("networkId"), r.FormValue("username"))
  if err != nil {
    http.Error(w, err.Error(), http.StatusInternalServerError)
  }
  defer rows.Close()
  for rows.Next() {
    if err = rows.Scan(&user.FirstName, &user.LastName, &user.Role, &user.Username,
                        &user.Secret, &user.Discoverable, &user.Network); err != nil {
      http.Error(w, err.Error(), http.StatusInternalServerError)
    } else {
      userList = append(userList, user)
    }
  }
  if err = rows.Err(); err != nil {
    http.Error(w, err.Error(), http.StatusInternalServerError)
  }

  return userList
}
<commit_msg>added \!network_id null clause to allusers db query<commit_after>package helpers

import (
	. "seeme/models"
  "fmt"

  "net/http"

	"database/sql"
  _ "github.com/go-sql-driver/mysql"
)

var db *sql.DB

func InitMySQLConnection() {
  db, _ = sql.Open("mysql", "root:#54nFr4nc15c0@/seeme_db")
}

func VerifyMySQLConnection(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
  if err := db.Ping(); err != nil {
    http.Error(w, err.Error(), http.StatusInternalServerError)
  }
  next(w, r)
}

func GetDatabaseInstance() (*sql.DB) {
	return db
}

func InsertNewUser(user User) (error) {
	_, err := db.Exec("INSERT INTO users (first_name, last_name, role, username, secret, discoverable, network_id) VALUES (?, ?, ?, ?, ?, ?, ?)",
                  user.FirstName, user.LastName, user.Role, user.Username,
                  user.Secret, user.Discoverable, nil)
	return err
}

func GetUserFromDB(username string) (User, error) {
  var user User
  row := db.QueryRow("select * from users where username = ?", username)
  err := row.Scan(&user.FirstName, &user.LastName, &user.Role, &user.Username, &user.Secret, &user.Discoverable, &user.Network)

  return user, err
}

func GetDiscoverableUsersFromDB(w http.ResponseWriter) ([]User) {
  var userList []User
  var user User

  rows, err := db.Query("select * from users where discoverable = ? AND !(network_id = NULL)", 1)
  if err != nil {
    http.Error(w, err.Error(), http.StatusInternalServerError)
  }
  defer rows.Close()
  for rows.Next() {
    if err = rows.Scan(&user.FirstName, &user.LastName, &user.Role, &user.Username,
                        &user.Secret, &user.Discoverable, &user.Network); err != nil {
      http.Error(w, err.Error(), http.StatusInternalServerError)
    } else {
      userList = append(userList, user)
    }
  }
  if err = rows.Err(); err != nil {
    http.Error(w, err.Error(), http.StatusInternalServerError)
  }

  return userList
}

func GetLocalUsersForNetwork(w http.ResponseWriter, r *http.Request) ([]User) {
  var networkId string
  row := db.QueryRow("select network_id from networks where network_id = ?", r.FormValue("networkId"))
  if err := row.Scan(&networkId); err != nil {
    http.Error(w, err.Error(), http.StatusInternalServerError)
  }
  fmt.Println(networkId)
  if networkId == "" {
    if err := insertNewNetwork(r); err != nil {
      http.Error(w, err.Error(), http.StatusInternalServerError)
    }
  }

  if err := updateUserNetwork(r); err != nil {
    http.Error(w, err.Error(), http.StatusInternalServerError)
  }
  return getExistingUsersForNetwork(w, r)
}

func insertNewNetwork(r *http.Request) (error) {
  _, err := db.Exec("INSERT INTO networks (network_id, ssid) VALUES (?, ?)",
                  r.FormValue("networkId"), r.FormValue("ssid"))
  return err
}

func updateUserNetwork(r *http.Request) (error) {
  _, err := db.Exec("UPDATE users SET network_id=? WHERE username= ?",
                  r.FormValue("networkId"), r.FormValue("username"))
  return err
}

func getExistingUsersForNetwork(w http.ResponseWriter, r *http.Request) ([]User) {
  var userList []User
  var user User

  rows, err := db.Query("SELECT first_name, last_name, role, username, secret, discoverable, ssid FROM users INNER JOIN networks USING (network_id) WHERE discoverable = ? AND network_id=? AND !(username = ?)",
    1, r.FormValue("networkId"), r.FormValue("username"))
  if err != nil {
    http.Error(w, err.Error(), http.StatusInternalServerError)
  }
  defer rows.Close()
  for rows.Next() {
    if err = rows.Scan(&user.FirstName, &user.LastName, &user.Role, &user.Username,
                        &user.Secret, &user.Discoverable, &user.Network); err != nil {
      http.Error(w, err.Error(), http.StatusInternalServerError)
    } else {
      userList = append(userList, user)
    }
  }
  if err = rows.Err(); err != nil {
    http.Error(w, err.Error(), http.StatusInternalServerError)
  }

  return userList
}
<|endoftext|>


<commit_before>package mediasort

import (
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/fatih/color"
	mediasearch "github.com/jpillora/media-sort/search"
	"github.com/jpillora/sizestr"

	"gopkg.in/fsnotify.v1"
)

//Config is a sorter configuration
type Config struct {
	Targets           []string `opts:"mode=arg,min=1"`
	TVDir             string   `opts:"help=tv series base directory (defaults to current directory)"`
	MovieDir          string   `opts:"help=movie base directory (defaults to current directory)"`
	PathConfig        `mode:"embedded"`
	Extensions        string        `opts:"help=types of files that should be sorted"`
	Concurrency       int           `opts:"help=search concurrency [warning] setting this too high can cause rate-limiting errors"`
	FileLimit         int           `opts:"help=maximum number of files to search"`
	AccuracyThreshold int           `opts:"help=filename match accuracy threshold" default:"is 95, perfect match is 100"`
	MinFileSize       sizestr.Bytes `opts:"help=minimum file size"`
	Recursive         bool          `opts:"help=also search through subdirectories"`
	DryRun            bool          `opts:"help=perform sort but don't actually move any files"`
	SkipHidden        bool          `opts:"help=skip dot files"`
	Action            Action        `opts:"help=how to tread the files (available <copy|link|move>)"`
	SymLink           bool          `opts:"help=use symlinks instead of hardlinks when linking the new files"`
	HardLink          bool          `opts:"help=use links instead of copying when treating the new files (deprecated, used for compatibility)"`
	Overwrite         bool          `opts:"help=overwrites duplicates"`
	OverwriteIfLarger bool          `opts:"help=overwrites duplicates if the new file is larger"`
	Watch             bool          `opts:"help=watch the specified directories for changes and re-sort on change"`
	WatchDelay        time.Duration `opts:"help=delay before next sort after a change"`
	Verbose           bool          `opts:"help=verbose logs"`
}

//fsSort is a media sorter
type fsSort struct {
	Config
	validExts map[string]bool
	sorts     map[string]*fileSort
	dirs      map[string]bool
	stats     struct {
		found, matched, moved int
	}
	linkType linkType
}

type fileSort struct {
	id     int
	path   string
	info   os.FileInfo
	result *Result
	err    error
}

// Action represent the way to treat the created files
type Action string

const (
	// MoveAction the new files
	MoveAction Action = "move"
	// LinkAction the new files
	LinkAction Action = "link"
	// CopyAction the new files
	CopyAction Action = "copy"
)

type linkType string

const (
	hardLink linkType = "hardLink"
	symLink  linkType = "symLink"
)

//FileSystemSort performs a media sort
//against the file system using the provided
//configuration
func FileSystemSort(c Config) error {
	if c.MovieDir == "" {
		c.MovieDir = "."
	}
	if c.TVDir == "" {
		c.TVDir = "."
	}
	if c.Watch && !c.Recursive {
		return errors.New("Recursive mode is required to watch directories")
	}
	if c.Overwrite && c.OverwriteIfLarger {
		return errors.New("Overwrite is already specified, overwrite-if-larger is redundant")
	}
	if c.Action == LinkAction && c.Overwrite {
		return errors.New("Link is already specified, Overwrite won't do anything")
	}
	switch c.Action {
	case MoveAction, LinkAction, CopyAction:
		break
	default:
		return errors.New("Provided action is not available")
	}
	//init fs sort
	fs := &fsSort{
		Config:    c,
		validExts: map[string]bool{},
		linkType:  hardLink,
	}
	if c.SymLink {
		fs.linkType = symLink
	}
	for _, e := range strings.Split(c.Extensions, ",") {
		fs.validExts["."+e] = true
	}
	//sort loop
	for {
		//reset state
		fs.sorts = map[string]*fileSort{}
		fs.dirs = map[string]bool{}
		//look for files
		if err := fs.scan(); err != nil {
			return err
		}
		//ensure we have dirs to watch
		if fs.Watch && len(fs.dirs) == 0 {
			return errors.New("No directories to watch")
		}
		if len(fs.sorts) > 0 {
			//moment of truth - sort all files!
			if err := fs.sortAllFiles(); err != nil {
				return err
			}
		}
		//watch directories
		if !c.Watch {
			break
		}
		if err := fs.watch(); err != nil {
			return err
		}
	}
	return nil
}

func (fs *fsSort) scan() error {
	fs.verbf("scanning targets...")
	//scan targets for media files
	for _, path := range fs.Targets {
		fs.verbf("scanning: %s", path)
		info, err := os.Stat(path)
		if err != nil {
			return err
		}
		if err = fs.add(path, info); err != nil {
			return err
		}
	}
	//ensure we found something
	if len(fs.sorts) == 0 && (!fs.Watch || len(fs.dirs) == 0) {
		return fmt.Errorf("No sortable files found (%d files checked)", fs.stats.found)
	}
	fs.verbf("scanned targets. found #%d", fs.stats.found)
	return nil
}

func (fs *fsSort) sortAllFiles() error {
	fs.verbf("sorting files...")
	//perform sort
	if fs.DryRun {
		log.Println(color.CyanString("[Dryrun]"))
	}
	//sort concurrency-many files at a time,
	//wait for all to complete and show errors
	queue := make(chan bool, fs.Concurrency)
	wg := &sync.WaitGroup{}
	sortFile := func(file *fileSort) {
		if err := fs.sortFile(file); err != nil {
			log.Printf("[#%d/%d] %s\n  └─> %s\n", file.id, len(fs.sorts), color.RedString(file.path), err)
		}
		<-queue
		wg.Done()
	}
	for _, file := range fs.sorts {
		wg.Add(1)
		queue <- true
		go sortFile(file)
	}
	wg.Wait()
	return nil
}

func (fs *fsSort) watch() error {
	if len(fs.dirs) == 0 {
		return errors.New("No directories to watch")
	}
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		return fmt.Errorf("Failed to create file watcher: %s", err)
	}
	for dir := range fs.dirs {
		if err := watcher.Add(dir); err != nil {
			return fmt.Errorf("Failed to watch directory: %s", err)
		}
		log.Printf("Watching %s for changes...", color.CyanString(dir))
	}
	select {
	case <-watcher.Events:
	case err := <-watcher.Errors:
		fs.verbf("watch error detected: %s", err)
	}
	go watcher.Close()
	log.Printf("Change detected, re-sorting in %s...", fs.WatchDelay)
	time.Sleep(fs.WatchDelay)
	return nil
}

func (fs *fsSort) add(path string, info os.FileInfo) error {
	//skip hidden files and directories
	if fs.SkipHidden && strings.HasPrefix(info.Name(), ".") {
		fs.verbf("skip hidden file: %s", path)
		return nil
	}
	//limit recursion depth
	if len(fs.sorts) >= fs.FileLimit {
		fs.verbf("skip file: %s. surpassed file limit: %d", path, fs.FileLimit)
		return nil
	}
	//add regular files (non-symlinks)
	if info.Mode().IsRegular() {
		fs.stats.found++
		//skip unmatched file types
		if !fs.validExts[filepath.Ext(path)] {
			fs.verbf("skip unmatched file ext: %s", path)
			return nil
		}
		//skip small files
		if info.Size() < int64(fs.MinFileSize) {
			fs.verbf("skip small file: %s", path)
			return nil
		}
		fs.sorts[path] = &fileSort{id: len(fs.sorts) + 1, path: path, info: info}
		fs.stats.matched++
		return nil
	}
	//recurse into directories
	if info.IsDir() {
		if !fs.Recursive {
			return errors.New("Recursive mode (-r) is required to sort directories")
		}
		//note directory
		fs.dirs[path] = true
		//add all files in dir
		infos, err := ioutil.ReadDir(path)
		if err != nil {
			return err
		}
		for _, info := range infos {
			p := filepath.Join(path, info.Name())
			//recurse
			if err := fs.add(p, info); err != nil {
				return err
			}
		}
	}
	fs.verbf("skip non-regular file: %s", path)
	//skip links,pipes,etc
	return nil
}

func (fs *fsSort) sortFile(file *fileSort) error {
	result, err := SortThreshold(file.path, fs.AccuracyThreshold)
	if err != nil {
		return err
	}
	newPath, err := result.PrettyPath(fs.PathConfig)
	if err != nil {
		return err
	}
	baseDir := ""
	switch mediasearch.MediaType(result.MType) {
	case mediasearch.Series:
		baseDir = fs.TVDir
	case mediasearch.Movie:
		baseDir = fs.MovieDir
	default:
		return fmt.Errorf("Invalid result type: %s", result.MType)
	}
	newPath = filepath.Join(baseDir, newPath)
	//check for subs.srt file
	pathSubs := strings.TrimSuffix(result.Path, filepath.Ext(result.Path)) + ".srt"
	_, err = os.Stat(pathSubs)
	hasSubs := err == nil
	subsExt := ""
	if hasSubs {
		subsExt = "," + color.GreenString("srt")
	}
	//found sort path
	log.Printf("[#%d/%d] %s\n  └─> %s", file.id, len(fs.sorts), color.GreenString(result.Path)+subsExt, color.GreenString(newPath)+subsExt)
	if fs.DryRun {
		return nil //don't actually move
	}
	if result.Path == newPath {
		return nil //already sorted
	}
	//check already exists
	if newInfo, err := os.Stat(newPath); err == nil {
		fileIsLarger := file.info.Size() > newInfo.Size()
		overwrite := fs.Overwrite || (fs.OverwriteIfLarger && fileIsLarger)
		//check if it the same file
		if !os.SameFile(file.info, newInfo) {
			if !overwrite {
				return fmt.Errorf("File already exists '%s' (try setting --overwrite)", newPath)
			}
		} else {
			return nil // File are the same
		}
	}

	// mkdir -p
	err = os.MkdirAll(filepath.Dir(newPath), 0755)
	if err != nil {
		return err //failed to mkdir
	}
	// treat the file
	err = fs.action(result.Path, newPath)
	if err != nil {
		return err //failed to move
	}
	//if .srt file exists for the file, treat it too
	if hasSubs {
		newPathSubs := strings.TrimSuffix(newPath, filepath.Ext(newPath)) + ".srt"
		fs.action(pathSubs, newPathSubs) //best-effort
	}
	return nil
}

func (fs *fsSort) verbf(f string, args ...interface{}) {
	if fs.Verbose {
		log.Printf(f, args...)
	}
}

func (fs *fsSort) action(src, dst string) (err error) {
	switch fs.Action {
	case MoveAction:
		err = move(src, dst)
	case CopyAction:
		err = copy(src, dst)
	case LinkAction:
		err = link(src, dst, fs.linkType)
	}
	return errors.New("unknown action")
}
<commit_msg>--hardlink flag forces the linkAction<commit_after>package mediasort

import (
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/fatih/color"
	mediasearch "github.com/jpillora/media-sort/search"
	"github.com/jpillora/sizestr"

	"gopkg.in/fsnotify.v1"
)

//Config is a sorter configuration
type Config struct {
	Targets           []string `opts:"mode=arg,min=1"`
	TVDir             string   `opts:"help=tv series base directory (defaults to current directory)"`
	MovieDir          string   `opts:"help=movie base directory (defaults to current directory)"`
	PathConfig        `mode:"embedded"`
	Extensions        string        `opts:"help=types of files that should be sorted"`
	Concurrency       int           `opts:"help=search concurrency [warning] setting this too high can cause rate-limiting errors"`
	FileLimit         int           `opts:"help=maximum number of files to search"`
	AccuracyThreshold int           `opts:"help=filename match accuracy threshold" default:"is 95, perfect match is 100"`
	MinFileSize       sizestr.Bytes `opts:"help=minimum file size"`
	Recursive         bool          `opts:"help=also search through subdirectories"`
	DryRun            bool          `opts:"help=perform sort but don't actually move any files"`
	SkipHidden        bool          `opts:"help=skip dot files"`
	Action            Action        `opts:"help=how to tread the files (available <copy|link|move>)"`
	SymLink           bool          `opts:"help=use symlinks instead of hardlinks when linking the new files"`
	HardLink          bool          `opts:"help=use links instead of copying when treating the new files (deprecated, overwrite the --action flag)"`
	Overwrite         bool          `opts:"help=overwrites duplicates"`
	OverwriteIfLarger bool          `opts:"help=overwrites duplicates if the new file is larger"`
	Watch             bool          `opts:"help=watch the specified directories for changes and re-sort on change"`
	WatchDelay        time.Duration `opts:"help=delay before next sort after a change"`
	Verbose           bool          `opts:"help=verbose logs"`
}

//fsSort is a media sorter
type fsSort struct {
	Config
	validExts map[string]bool
	sorts     map[string]*fileSort
	dirs      map[string]bool
	stats     struct {
		found, matched, moved int
	}
	linkType linkType
}

type fileSort struct {
	id     int
	path   string
	info   os.FileInfo
	result *Result
	err    error
}

// Action represent the way to treat the created files
type Action string

const (
	// MoveAction the new files
	MoveAction Action = "move"
	// LinkAction the new files
	LinkAction Action = "link"
	// CopyAction the new files
	CopyAction Action = "copy"
)

type linkType string

const (
	hardLink linkType = "hardLink"
	symLink  linkType = "symLink"
)

//FileSystemSort performs a media sort
//against the file system using the provided
//configuration
func FileSystemSort(c Config) error {
	if c.MovieDir == "" {
		c.MovieDir = "."
	}
	if c.TVDir == "" {
		c.TVDir = "."
	}
	if c.Watch && !c.Recursive {
		return errors.New("Recursive mode is required to watch directories")
	}
	if c.Overwrite && c.OverwriteIfLarger {
		return errors.New("Overwrite is already specified, overwrite-if-larger is redundant")
	}
	if c.Action == LinkAction && c.Overwrite {
		return errors.New("Link is already specified, Overwrite won't do anything")
	}
	if c.HardLink {
		c.Action = LinkAction
	}
	switch c.Action {
	case MoveAction, LinkAction, CopyAction:
		break
	default:
		return errors.New("Provided action is not available")
	}
	//init fs sort
	fs := &fsSort{
		Config:    c,
		validExts: map[string]bool{},
		linkType:  hardLink,
	}
	if c.SymLink {
		fs.linkType = symLink
	}
	for _, e := range strings.Split(c.Extensions, ",") {
		fs.validExts["."+e] = true
	}
	//sort loop
	for {
		//reset state
		fs.sorts = map[string]*fileSort{}
		fs.dirs = map[string]bool{}
		//look for files
		if err := fs.scan(); err != nil {
			return err
		}
		//ensure we have dirs to watch
		if fs.Watch && len(fs.dirs) == 0 {
			return errors.New("No directories to watch")
		}
		if len(fs.sorts) > 0 {
			//moment of truth - sort all files!
			if err := fs.sortAllFiles(); err != nil {
				return err
			}
		}
		//watch directories
		if !c.Watch {
			break
		}
		if err := fs.watch(); err != nil {
			return err
		}
	}
	return nil
}

func (fs *fsSort) scan() error {
	fs.verbf("scanning targets...")
	//scan targets for media files
	for _, path := range fs.Targets {
		fs.verbf("scanning: %s", path)
		info, err := os.Stat(path)
		if err != nil {
			return err
		}
		if err = fs.add(path, info); err != nil {
			return err
		}
	}
	//ensure we found something
	if len(fs.sorts) == 0 && (!fs.Watch || len(fs.dirs) == 0) {
		return fmt.Errorf("No sortable files found (%d files checked)", fs.stats.found)
	}
	fs.verbf("scanned targets. found #%d", fs.stats.found)
	return nil
}

func (fs *fsSort) sortAllFiles() error {
	fs.verbf("sorting files...")
	//perform sort
	if fs.DryRun {
		log.Println(color.CyanString("[Dryrun]"))
	}
	//sort concurrency-many files at a time,
	//wait for all to complete and show errors
	queue := make(chan bool, fs.Concurrency)
	wg := &sync.WaitGroup{}
	sortFile := func(file *fileSort) {
		if err := fs.sortFile(file); err != nil {
			log.Printf("[#%d/%d] %s\n  └─> %s\n", file.id, len(fs.sorts), color.RedString(file.path), err)
		}
		<-queue
		wg.Done()
	}
	for _, file := range fs.sorts {
		wg.Add(1)
		queue <- true
		go sortFile(file)
	}
	wg.Wait()
	return nil
}

func (fs *fsSort) watch() error {
	if len(fs.dirs) == 0 {
		return errors.New("No directories to watch")
	}
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		return fmt.Errorf("Failed to create file watcher: %s", err)
	}
	for dir := range fs.dirs {
		if err := watcher.Add(dir); err != nil {
			return fmt.Errorf("Failed to watch directory: %s", err)
		}
		log.Printf("Watching %s for changes...", color.CyanString(dir))
	}
	select {
	case <-watcher.Events:
	case err := <-watcher.Errors:
		fs.verbf("watch error detected: %s", err)
	}
	go watcher.Close()
	log.Printf("Change detected, re-sorting in %s...", fs.WatchDelay)
	time.Sleep(fs.WatchDelay)
	return nil
}

func (fs *fsSort) add(path string, info os.FileInfo) error {
	//skip hidden files and directories
	if fs.SkipHidden && strings.HasPrefix(info.Name(), ".") {
		fs.verbf("skip hidden file: %s", path)
		return nil
	}
	//limit recursion depth
	if len(fs.sorts) >= fs.FileLimit {
		fs.verbf("skip file: %s. surpassed file limit: %d", path, fs.FileLimit)
		return nil
	}
	//add regular files (non-symlinks)
	if info.Mode().IsRegular() {
		fs.stats.found++
		//skip unmatched file types
		if !fs.validExts[filepath.Ext(path)] {
			fs.verbf("skip unmatched file ext: %s", path)
			return nil
		}
		//skip small files
		if info.Size() < int64(fs.MinFileSize) {
			fs.verbf("skip small file: %s", path)
			return nil
		}
		fs.sorts[path] = &fileSort{id: len(fs.sorts) + 1, path: path, info: info}
		fs.stats.matched++
		return nil
	}
	//recurse into directories
	if info.IsDir() {
		if !fs.Recursive {
			return errors.New("Recursive mode (-r) is required to sort directories")
		}
		//note directory
		fs.dirs[path] = true
		//add all files in dir
		infos, err := ioutil.ReadDir(path)
		if err != nil {
			return err
		}
		for _, info := range infos {
			p := filepath.Join(path, info.Name())
			//recurse
			if err := fs.add(p, info); err != nil {
				return err
			}
		}
	}
	fs.verbf("skip non-regular file: %s", path)
	//skip links,pipes,etc
	return nil
}

func (fs *fsSort) sortFile(file *fileSort) error {
	result, err := SortThreshold(file.path, fs.AccuracyThreshold)
	if err != nil {
		return err
	}
	newPath, err := result.PrettyPath(fs.PathConfig)
	if err != nil {
		return err
	}
	baseDir := ""
	switch mediasearch.MediaType(result.MType) {
	case mediasearch.Series:
		baseDir = fs.TVDir
	case mediasearch.Movie:
		baseDir = fs.MovieDir
	default:
		return fmt.Errorf("Invalid result type: %s", result.MType)
	}
	newPath = filepath.Join(baseDir, newPath)
	//check for subs.srt file
	pathSubs := strings.TrimSuffix(result.Path, filepath.Ext(result.Path)) + ".srt"
	_, err = os.Stat(pathSubs)
	hasSubs := err == nil
	subsExt := ""
	if hasSubs {
		subsExt = "," + color.GreenString("srt")
	}
	//found sort path
	log.Printf("[#%d/%d] %s\n  └─> %s", file.id, len(fs.sorts), color.GreenString(result.Path)+subsExt, color.GreenString(newPath)+subsExt)
	if fs.DryRun {
		return nil //don't actually move
	}
	if result.Path == newPath {
		return nil //already sorted
	}
	//check already exists
	if newInfo, err := os.Stat(newPath); err == nil {
		fileIsLarger := file.info.Size() > newInfo.Size()
		overwrite := fs.Overwrite || (fs.OverwriteIfLarger && fileIsLarger)
		//check if it the same file
		if !os.SameFile(file.info, newInfo) {
			if !overwrite {
				return fmt.Errorf("File already exists '%s' (try setting --overwrite)", newPath)
			}
		} else {
			return nil // File are the same
		}
	}

	// mkdir -p
	err = os.MkdirAll(filepath.Dir(newPath), 0755)
	if err != nil {
		return err //failed to mkdir
	}
	// treat the file
	err = fs.action(result.Path, newPath)
	if err != nil {
		return err //failed to move
	}
	//if .srt file exists for the file, treat it too
	if hasSubs {
		newPathSubs := strings.TrimSuffix(newPath, filepath.Ext(newPath)) + ".srt"
		fs.action(pathSubs, newPathSubs) //best-effort
	}
	return nil
}

func (fs *fsSort) verbf(f string, args ...interface{}) {
	if fs.Verbose {
		log.Printf(f, args...)
	}
}

func (fs *fsSort) action(src, dst string) (err error) {
	switch fs.Action {
	case MoveAction:
		err = move(src, dst)
	case CopyAction:
		err = copy(src, dst)
	case LinkAction:
		err = link(src, dst, fs.linkType)
	}
	return errors.New("unknown action")
}
<|endoftext|>


<commit_before>// +build windows

package host

import (
	"context"
	"fmt"
	"os"
	"runtime"
	"strings"
	"sync/atomic"
	"syscall"
	"time"
	"unsafe"

	"github.com/shirou/gopsutil/internal/common"
	process "github.com/shirou/gopsutil/process"
	"golang.org/x/sys/windows/registry"
)

var (
	procGetSystemTimeAsFileTime = common.Modkernel32.NewProc("GetSystemTimeAsFileTime")
	procGetTickCount32          = common.Modkernel32.NewProc("GetTickCount")
	procGetTickCount64          = common.Modkernel32.NewProc("GetTickCount64")
	procRtlGetVersion           = common.ModNt.NewProc("RtlGetVersion")
)

// https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/wdm/ns-wdm-_osversioninfoexw
type osVersionInfoExW struct {
	dwOSVersionInfoSize uint32
	dwMajorVersion      uint32
	dwMinorVersion      uint32
	dwBuildNumber       uint32
	dwPlatformId        uint32
	szCSDVersion        [128]uint16
	wServicePackMajor   uint16
	wServicePackMinor   uint16
	wSuiteMask          uint16
	wProductType        uint8
	wReserved           uint8
}

func Info() (*InfoStat, error) {
	return InfoWithContext(context.Background())
}

func InfoWithContext(ctx context.Context) (*InfoStat, error) {
	ret := &InfoStat{
		OS: runtime.GOOS,
	}

	{
		hostname, err := os.Hostname()
		if err == nil {
			ret.Hostname = hostname
		}
	}

	{
		platform, family, version, err := PlatformInformation()
		if err == nil {
			ret.Platform = platform
			ret.PlatformFamily = family
			ret.PlatformVersion = version
		} else {
			return ret, err
		}
	}

	{
		boot, err := BootTime()
		if err == nil {
			ret.BootTime = boot
			ret.Uptime, _ = Uptime()
		}
	}

	{
		hostID, err := getMachineGuid()
		if err == nil {
			ret.HostID = strings.ToLower(hostID)
		}
	}

	{
		procs, err := process.Pids()
		if err == nil {
			ret.Procs = uint64(len(procs))
		}
	}

	return ret, nil
}

func getMachineGuid() (string, error) {
	k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Cryptography`, registry.QUERY_VALUE)
	if err != nil {
		return "", err
	}
	defer k.Close()
	hostID, _, err := k.GetStringValue("MachineGuid")
	if err != nil {
		return "", err
	}
	return hostID, nil
}

func Uptime() (uint64, error) {
	return UptimeWithContext(context.Background())
}

func UptimeWithContext(ctx context.Context) (uint64, error) {
	procGetTickCount := procGetTickCount64
	err := procGetTickCount64.Find()
	if err != nil {
		procGetTickCount = procGetTickCount32 // handle WinXP, but keep in mind that "the time will wrap around to zero if the system is run continuously for 49.7 days." from MSDN
	}
	r1, _, lastErr := syscall.Syscall(procGetTickCount.Addr(), 0, 0, 0, 0)
	if lastErr != 0 {
		return 0, lastErr
	}
	return uint64((time.Duration(r1) * time.Millisecond).Seconds()), nil
}

func bootTimeFromUptime(up uint64) uint64 {
	return uint64(time.Now().Unix()) - up
}

// cachedBootTime must be accessed via atomic.Load/StoreUint64
var cachedBootTime uint64

func BootTime() (uint64, error) {
	return BootTimeWithContext(context.Background())
}

func BootTimeWithContext(ctx context.Context) (uint64, error) {
	t := atomic.LoadUint64(&cachedBootTime)
	if t != 0 {
		return t, nil
	}
	up, err := Uptime()
	if err != nil {
		return 0, err
	}
	t = bootTimeFromUptime(up)
	atomic.StoreUint64(&cachedBootTime, t)
	return t, nil
}

func PlatformInformation() (platform string, family string, version string, err error) {
	return PlatformInformationWithContext(context.Background())
}

func PlatformInformationWithContext(ctx context.Context) (platform string, family string, version string, err error) {
	// GetVersionEx lies on Windows 8.1 and returns as Windows 8 if we don't declare compatibility in manifest
	// RtlGetVersion bypasses this lying layer and returns the true Windows version
	// https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/wdm/nf-wdm-rtlgetversion
	// https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/wdm/ns-wdm-_osversioninfoexw
	var osInfo osVersionInfoExW
	osInfo.dwOSVersionInfoSize = uint32(unsafe.Sizeof(osInfo))
	ret, _, err := procRtlGetVersion.Call(uintptr(unsafe.Pointer(&osInfo)))
	if ret != 0 {
		return
	}

	// Platform
	k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE)
	if err != nil {
		return
	}
	defer k.Close()
	platform, _, err = k.GetStringValue("ProductName")
	if err != nil {
		return
	}
	if !strings.HasPrefix(platform, "Microsoft") {
		platform = "Microsoft " + platform
	}
	csd, _, err := k.GetStringValue("CSDVersion")
	if err == nil {
		platform += " " + csd
	}

	// PlatformFamily
	switch osInfo.wProductType {
	case 1:
		family = "Standalone Workstation"
	case 2:
		family = "Server (Domain Controller)"
	case 3:
		family = "Server"
	}

	// Platform Version
	version = fmt.Sprintf("%d.%d.%d Build %d", osInfo.dwMajorVersion, osInfo.dwMinorVersion, osInfo.dwBuildNumber, osInfo.dwBuildNumber)

	return
}

func Users() ([]UserStat, error) {
	return UsersWithContext(context.Background())
}

func UsersWithContext(ctx context.Context) ([]UserStat, error) {
	var ret []UserStat

	return ret, nil
}

func SensorsTemperatures() ([]TemperatureStat, error) {
	return SensorsTemperaturesWithContext(context.Background())
}

func SensorsTemperaturesWithContext(ctx context.Context) ([]TemperatureStat, error) {
	return []TemperatureStat{}, common.ErrNotImplementedError
}

func Virtualization() (string, string, error) {
	return VirtualizationWithContext(context.Background())
}

func VirtualizationWithContext(ctx context.Context) (string, string, error) {
	return "", "", common.ErrNotImplementedError
}

func KernelVersion() (string, error) {
	return KernelVersionWithContext(context.Background())
}

func KernelVersionWithContext(ctx context.Context) (string, error) {
	_, _, version, err := PlatformInformation()
	return version, err
}
<commit_msg>Revert "[host][windows] Refactor code to query registry via golang.org/x/sys/windows/registry"<commit_after>// +build windows

package host

import (
	"context"
	"fmt"
	"os"
	"runtime"
	"strings"
	"sync/atomic"
	"syscall"
	"time"
	"unsafe"

	"github.com/shirou/gopsutil/internal/common"
	process "github.com/shirou/gopsutil/process"
	"golang.org/x/sys/windows"
	"golang.org/x/sys/windows/registry"
)

var (
	procGetSystemTimeAsFileTime = common.Modkernel32.NewProc("GetSystemTimeAsFileTime")
	procGetTickCount32          = common.Modkernel32.NewProc("GetTickCount")
	procGetTickCount64          = common.Modkernel32.NewProc("GetTickCount64")
	procRtlGetVersion           = common.ModNt.NewProc("RtlGetVersion")
)

// https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/wdm/ns-wdm-_osversioninfoexw
type osVersionInfoExW struct {
	dwOSVersionInfoSize uint32
	dwMajorVersion      uint32
	dwMinorVersion      uint32
	dwBuildNumber       uint32
	dwPlatformId        uint32
	szCSDVersion        [128]uint16
	wServicePackMajor   uint16
	wServicePackMinor   uint16
	wSuiteMask          uint16
	wProductType        uint8
	wReserved           uint8
}

func Info() (*InfoStat, error) {
	return InfoWithContext(context.Background())
}

func InfoWithContext(ctx context.Context) (*InfoStat, error) {
	ret := &InfoStat{
		OS: runtime.GOOS,
	}

	{
		hostname, err := os.Hostname()
		if err == nil {
			ret.Hostname = hostname
		}
	}

	{
		platform, family, version, err := PlatformInformation()
		if err == nil {
			ret.Platform = platform
			ret.PlatformFamily = family
			ret.PlatformVersion = version
		} else {
			return ret, err
		}
	}

	{
		boot, err := BootTime()
		if err == nil {
			ret.BootTime = boot
			ret.Uptime, _ = Uptime()
		}
	}

	{
		hostID, err := getMachineGuid()
		if err == nil {
			ret.HostID = strings.ToLower(hostID)
		}
	}

	{
		procs, err := process.Pids()
		if err == nil {
			ret.Procs = uint64(len(procs))
		}
	}

	return ret, nil
}

func getMachineGuid() (string, error) {
	var h windows.Handle
	err := windows.RegOpenKeyEx(windows.HKEY_LOCAL_MACHINE, windows.StringToUTF16Ptr(`SOFTWARE\Microsoft\Cryptography`), 0, windows.KEY_READ|windows.KEY_WOW64_64KEY, &h)
	if err != nil {
		return "", err
	}
	defer windows.RegCloseKey(h)

	const windowsRegBufLen = 74 // len(`{`) + len(`abcdefgh-1234-456789012-123345456671` * 2) + len(`}`) // 2 == bytes/UTF16
	const uuidLen = 36

	var regBuf [windowsRegBufLen]uint16
	bufLen := uint32(windowsRegBufLen)
	var valType uint32
	err = windows.RegQueryValueEx(h, windows.StringToUTF16Ptr(`MachineGuid`), nil, &valType, (*byte)(unsafe.Pointer(&regBuf[0])), &bufLen)
	if err != nil {
		return "", err
	}

	hostID := windows.UTF16ToString(regBuf[:])
	hostIDLen := len(hostID)
	if hostIDLen != uuidLen {
		return "", fmt.Errorf("HostID incorrect: %q\n", hostID)
	}

	return hostID, nil
}

func Uptime() (uint64, error) {
	return UptimeWithContext(context.Background())
}

func UptimeWithContext(ctx context.Context) (uint64, error) {
	procGetTickCount := procGetTickCount64
	err := procGetTickCount64.Find()
	if err != nil {
		procGetTickCount = procGetTickCount32 // handle WinXP, but keep in mind that "the time will wrap around to zero if the system is run continuously for 49.7 days." from MSDN
	}
	r1, _, lastErr := syscall.Syscall(procGetTickCount.Addr(), 0, 0, 0, 0)
	if lastErr != 0 {
		return 0, lastErr
	}
	return uint64((time.Duration(r1) * time.Millisecond).Seconds()), nil
}

func bootTimeFromUptime(up uint64) uint64 {
	return uint64(time.Now().Unix()) - up
}

// cachedBootTime must be accessed via atomic.Load/StoreUint64
var cachedBootTime uint64

func BootTime() (uint64, error) {
	return BootTimeWithContext(context.Background())
}

func BootTimeWithContext(ctx context.Context) (uint64, error) {
	t := atomic.LoadUint64(&cachedBootTime)
	if t != 0 {
		return t, nil
	}
	up, err := Uptime()
	if err != nil {
		return 0, err
	}
	t = bootTimeFromUptime(up)
	atomic.StoreUint64(&cachedBootTime, t)
	return t, nil
}

func PlatformInformation() (platform string, family string, version string, err error) {
	return PlatformInformationWithContext(context.Background())
}

func PlatformInformationWithContext(ctx context.Context) (platform string, family string, version string, err error) {
	// GetVersionEx lies on Windows 8.1 and returns as Windows 8 if we don't declare compatibility in manifest
	// RtlGetVersion bypasses this lying layer and returns the true Windows version
	// https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/wdm/nf-wdm-rtlgetversion
	// https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/wdm/ns-wdm-_osversioninfoexw
	var osInfo osVersionInfoExW
	osInfo.dwOSVersionInfoSize = uint32(unsafe.Sizeof(osInfo))
	ret, _, err := procRtlGetVersion.Call(uintptr(unsafe.Pointer(&osInfo)))
	if ret != 0 {
		return
	}

	// Platform
	k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE)
	if err != nil {
		return
	}
	defer k.Close()
	platform, _, err = k.GetStringValue("ProductName")
	if err != nil {
		return
	}
	if !strings.HasPrefix(platform, "Microsoft") {
		platform = "Microsoft " + platform
	}
	csd, _, err := k.GetStringValue("CSDVersion")
	if err == nil {
		platform += " " + csd
	}

	// PlatformFamily
	switch osInfo.wProductType {
	case 1:
		family = "Standalone Workstation"
	case 2:
		family = "Server (Domain Controller)"
	case 3:
		family = "Server"
	}

	// Platform Version
	version = fmt.Sprintf("%d.%d.%d Build %d", osInfo.dwMajorVersion, osInfo.dwMinorVersion, osInfo.dwBuildNumber, osInfo.dwBuildNumber)

	return
}

func Users() ([]UserStat, error) {
	return UsersWithContext(context.Background())
}

func UsersWithContext(ctx context.Context) ([]UserStat, error) {
	var ret []UserStat

	return ret, nil
}

func SensorsTemperatures() ([]TemperatureStat, error) {
	return SensorsTemperaturesWithContext(context.Background())
}

func SensorsTemperaturesWithContext(ctx context.Context) ([]TemperatureStat, error) {
	return []TemperatureStat{}, common.ErrNotImplementedError
}

func Virtualization() (string, string, error) {
	return VirtualizationWithContext(context.Background())
}

func VirtualizationWithContext(ctx context.Context) (string, string, error) {
	return "", "", common.ErrNotImplementedError
}

func KernelVersion() (string, error) {
	return KernelVersionWithContext(context.Background())
}

func KernelVersionWithContext(ctx context.Context) (string, error) {
	_, _, version, err := PlatformInformation()
	return version, err
}
<|endoftext|>