<commit_before>// Copyright 2017 Istio Authors
//
// 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 log provides the canonical logging functionality used by Go-based
// Istio components.
//
// Istio's logging subsystem is built on top of the [Zap](https://godoc.org/go.uber.org/zap) package.
// High performance scenarios should use the Error, Warn, Info, and Debug methods. Lower perf
// scenarios can use the more expensive convenience methods such as Debugf and Warnw.
//
// The package provides direct integration with the Cobra command-line processor which makes it
// easy to build programs that use a consistent interface for logging. Here's an example
// of a simple Cobra-based program using this log package:
//
//		func main() {
//			// get the default logging options
//			options := log.NewOptions()
//
//			rootCmd := &cobra.Command{
//				Run: func(cmd *cobra.Command, args []string) {
//
//					// configure the logging system
//					if err := log.Configure(options); err != nil {
//                      // print an error and quit
//                  }
//
//					// output some logs
//					log.Info("Hello")
//					log.Sync()
//				},
//			}
//
//			// add logging-specific flags to the cobra command
//			options.AttachCobraFlags(rootCmd)
//			rootCmd.SetArgs(os.Args[1:])
//			rootCmd.Execute()
//		}
//
// Once configured, this package intercepts the output of the standard golang "log" package as well as anything
// sent to the global zap logger (zap.L()).
package log

import (
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"go.uber.org/zap/zapgrpc"
	"google.golang.org/grpc/grpclog"
)

// Global variables against which all our logging occurs.
var logger *zap.Logger = zap.NewNop()
var sugar *zap.SugaredLogger = logger.Sugar()

// Configure initializes Istio's logging subsystem.
//
// You typically call this once at process startup.
// Once this call returns, the logging system is ready to accept data.
func Configure(options *Options) error {
	return configure(options, func(c *zap.Config) (*zap.Logger, error) { return c.Build() })
}

type builder func(c *zap.Config) (*zap.Logger, error)

func configure(options *Options, b builder) error {
	outputLevel, err := options.GetOutputLevel()
	if err != nil {
		return err
	}

	stackTraceLevel, err := options.GetStackTraceLevel()
	if err != nil {
		return err
	}

	if outputLevel == None {
		// stick with the Nop default
		logger = zap.NewNop()
		sugar = logger.Sugar()
		logger = zap.NewNop()
		sugar = logger.Sugar()
		return nil
	}

	zapConfig := zap.Config{
		Level:       zap.NewAtomicLevelAt(outputLevel),
		Development: false,

		Sampling: &zap.SamplingConfig{
			Initial:    100,
			Thereafter: 100,
		},

		Encoding: "console",
		EncoderConfig: zapcore.EncoderConfig{
			TimeKey:        "time",
			LevelKey:       "level",
			NameKey:        "logger",
			CallerKey:      "caller",
			MessageKey:     "msg",
			StacktraceKey:  "stack",
			LineEnding:     zapcore.DefaultLineEnding,
			EncodeLevel:    zapcore.LowercaseLevelEncoder,
			EncodeCaller:   zapcore.ShortCallerEncoder,
			EncodeTime:     zapcore.ISO8601TimeEncoder,
			EncodeDuration: zapcore.StringDurationEncoder,
		},

		OutputPaths:       options.OutputPaths,
		ErrorOutputPaths:  []string{"stderr"},
		DisableCaller:     !options.IncludeCallerSourceLocation,
		DisableStacktrace: stackTraceLevel == None,
	}

	if options.JSONEncoding {
		zapConfig.Encoding = "json"
	}

	l, err := b(&zapConfig)
	if err != nil {
		return err
	}

	logger = l.WithOptions(zap.AddCallerSkip(1), zap.AddStacktrace(stackTraceLevel))
	sugar = logger.Sugar()

	// capture global zap logging and force it through our logger
	_ = zap.ReplaceGlobals(l)

	// capture standard golang "log" package output and force it through our logger
	_ = zap.RedirectStdLog(logger)

	// capture gRPC logging
	grpclog.SetLogger(zapgrpc.NewLogger(logger.WithOptions(zap.AddCallerSkip(2))))

	return nil
}

// Debug outputs a message at debug level.
// This call is a wrapper around [Logger.Debug](https://godoc.org/go.uber.org/zap#Logger.Debug)
func Debug(msg string, fields ...zapcore.Field) {
	logger.Debug(msg, fields...)
}

// Debuga uses fmt.Sprint to construct and log a message at debug level.
// This call is a wrapper around [Sugaredlogger.Debug](https://godoc.org/go.uber.org/zap#Sugaredlogger.Debug)
func Debuga(args ...interface{}) {
	sugar.Debug(args...)
}

// Debugf uses fmt.Sprintf to construct and log a message at debug level.
// This call is a wrapper around [Sugaredlogger.Debugf](https://godoc.org/go.uber.org/zap#Sugaredlogger.Debugf)
func Debugf(template string, args ...interface{}) {
	sugar.Debugf(template, args...)
}

// Debugw logs a message at debug level with some additional context.
// This call is a wrapper around [Sugaredlogger.Debugw](https://godoc.org/go.uber.org/zap#Sugaredlogger.Debugw)
func Debugw(msg string, keysAndValues ...interface{}) {
	sugar.Debugw(msg, keysAndValues...)
}

// DebugEnabled returns whether output of messages at the debug level is currently enabled.
func DebugEnabled() bool {
	return logger.Core().Enabled(zap.DebugLevel)
}

// Error outputs a message at error level.
// This call is a wrapper around [logger.Error](https://godoc.org/go.uber.org/zap#logger.Error)
func Error(msg string, fields ...zapcore.Field) {
	logger.Error(msg, fields...)
}

// Errora uses fmt.Sprint to construct and log a message at error level.
// This call is a wrapper around [Sugaredlogger.Error](https://godoc.org/go.uber.org/zap#Sugaredlogger.Error)
func Errora(args ...interface{}) {
	sugar.Error(args...)
}

// Errorf uses fmt.Sprintf to construct and log a message at error level.
// This call is a wrapper around [Sugaredlogger.Errorf](https://godoc.org/go.uber.org/zap#Sugaredlogger.Errorf)
func Errorf(template string, args ...interface{}) {
	sugar.Errorf(template, args...)
}

// Errorw logs a message at error level with some additional context.
// This call is a wrapper around [Sugaredlogger.Errorw](https://godoc.org/go.uber.org/zap#Sugaredlogger.Errorw)
func Errorw(msg string, keysAndValues ...interface{}) {
	sugar.Errorw(msg, keysAndValues...)
}

// ErrorEnabled returns whether output of messages at the error level is currently enabled.
func ErrorEnabled() bool {
	return logger.Core().Enabled(zap.ErrorLevel)
}

// Warn outputs a message at warn level.
// This call is a wrapper around [logger.Warn](https://godoc.org/go.uber.org/zap#logger.Warn)
func Warn(msg string, fields ...zapcore.Field) {
	logger.Warn(msg, fields...)
}

// Warna uses fmt.Sprint to construct and log a message at warn level.
// This call is a wrapper around [Sugaredlogger.Warn](https://godoc.org/go.uber.org/zap#Sugaredlogger.Warn)
func Warna(args ...interface{}) {
	sugar.Warn(args...)
}

// Warnf uses fmt.Sprintf to construct and log a message at warn level.
// This call is a wrapper around [Sugaredlogger.Warnf](https://godoc.org/go.uber.org/zap#Sugaredlogger.Warnf)
func Warnf(template string, args ...interface{}) {
	sugar.Warnf(template, args...)
}

// Warnw logs a message at warn level with some additional context.
// This call is a wrapper around [Sugaredlogger.Warnw](https://godoc.org/go.uber.org/zap#Sugaredlogger.Warnw)
func Warnw(msg string, keysAndValues ...interface{}) {
	sugar.Warnw(msg, keysAndValues...)
}

// WarnEnabled returns whether output of messages at the warn level is currently enabled.
func WarnEnabled() bool {
	return logger.Core().Enabled(zap.WarnLevel)
}

// Info outputs a message at information level.
// This call is a wrapper around [logger.Info](https://godoc.org/go.uber.org/zap#logger.Info)
func Info(msg string, fields ...zapcore.Field) {
	logger.Info(msg, fields...)
}

// Infoa uses fmt.Sprint to construct and log a message at info level.
// This call is a wrapper around [Sugaredlogger.Info](https://godoc.org/go.uber.org/zap#Sugaredlogger.Info)
func Infoa(args ...interface{}) {
	sugar.Info(args...)
}

// Infof uses fmt.Sprintf to construct and log a message at info level.
// This call is a wrapper around [Sugaredlogger.Infof](https://godoc.org/go.uber.org/zap#Sugaredlogger.Infof)
func Infof(template string, args ...interface{}) {
	sugar.Infof(template, args...)
}

// Infow logs a message at info level with some additional context.
// This call is a wrapper around [Sugaredlogger.Infow](https://godoc.org/go.uber.org/zap#Sugaredlogger.Infow)
func Infow(msg string, keysAndValues ...interface{}) {
	sugar.Infow(msg, keysAndValues...)
}

// InfoEnabled returns whether output of messages at the info level is currently enabled.
func InfoEnabled() bool {
	return logger.Core().Enabled(zap.InfoLevel)
}

// With creates a child logger and adds structured context to it. Fields added
// to the child don't affect the parent, and vice versa.
// This call is a wrapper around [logger.With](https://godoc.org/go.uber.org/zap#logger.With)
func With(fields ...zapcore.Field) *zap.Logger {
	return logger.With(fields...)
}

// Sync flushes any buffered log entries.
// Processes should normally take care to call Sync before exiting.
// This call is a wrapper around [logger.Sync](https://godoc.org/go.uber.org/zap#logger.Sync)
func Sync() {
	logger.Sync()
}
<commit_msg>Log package fixes (#2259)<commit_after>// Copyright 2017 Istio Authors
//
// 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 log provides the canonical logging functionality used by Go-based
// Istio components.
//
// Istio's logging subsystem is built on top of the [Zap](https://godoc.org/go.uber.org/zap) package.
// High performance scenarios should use the Error, Warn, Info, and Debug methods. Lower perf
// scenarios can use the more expensive convenience methods such as Debugf and Warnw.
//
// The package provides direct integration with the Cobra command-line processor which makes it
// easy to build programs that use a consistent interface for logging. Here's an example
// of a simple Cobra-based program using this log package:
//
//		func main() {
//			// get the default logging options
//			options := log.NewOptions()
//
//			rootCmd := &cobra.Command{
//				Run: func(cmd *cobra.Command, args []string) {
//
//					// configure the logging system
//					if err := log.Configure(options); err != nil {
//                      // print an error and quit
//                  }
//
//					// output some logs
//					log.Info("Hello")
//					log.Sync()
//				},
//			}
//
//			// add logging-specific flags to the cobra command
//			options.AttachCobraFlags(rootCmd)
//			rootCmd.SetArgs(os.Args[1:])
//			rootCmd.Execute()
//		}
//
// Once configured, this package intercepts the output of the standard golang "log" package as well as anything
// sent to the global zap logger (zap.L()).
package log

import (
	"time"

	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"go.uber.org/zap/zapgrpc"
	"google.golang.org/grpc/grpclog"
)

// Global variables against which all our logging occurs.
var logger *zap.Logger = zap.NewNop()
var sugar *zap.SugaredLogger = logger.Sugar()

// Configure initializes Istio's logging subsystem.
//
// You typically call this once at process startup.
// Once this call returns, the logging system is ready to accept data.
func Configure(options *Options) error {
	return configure(options, func(c *zap.Config) (*zap.Logger, error) { return c.Build() })
}

type builder func(c *zap.Config) (*zap.Logger, error)

func configure(options *Options, b builder) error {
	outputLevel, err := options.GetOutputLevel()
	if err != nil {
		return err
	}

	stackTraceLevel, err := options.GetStackTraceLevel()
	if err != nil {
		return err
	}

	if outputLevel == None {
		// stick with the Nop default
		logger = zap.NewNop()
		sugar = logger.Sugar()
		logger = zap.NewNop()
		sugar = logger.Sugar()
		return nil
	}

	zapConfig := zap.Config{
		Level:       zap.NewAtomicLevelAt(outputLevel),
		Development: false,

		Sampling: &zap.SamplingConfig{
			Initial:    100,
			Thereafter: 100,
		},

		Encoding: "console",
		EncoderConfig: zapcore.EncoderConfig{
			TimeKey:        "time",
			LevelKey:       "level",
			NameKey:        "logger",
			CallerKey:      "caller",
			MessageKey:     "msg",
			StacktraceKey:  "stack",
			LineEnding:     zapcore.DefaultLineEnding,
			EncodeLevel:    zapcore.LowercaseLevelEncoder,
			EncodeCaller:   zapcore.ShortCallerEncoder,
			EncodeDuration: zapcore.StringDurationEncoder,

			EncodeTime: func(t time.Time, enc zapcore.PrimitiveArrayEncoder) {
				enc.AppendString(t.UTC().Format("2006-01-02T15:04:05.000Z0700"))
			},
		},

		OutputPaths:       options.OutputPaths,
		ErrorOutputPaths:  []string{"stderr"},
		DisableCaller:     !options.IncludeCallerSourceLocation,
		DisableStacktrace: stackTraceLevel == None,
	}

	if options.JSONEncoding {
		zapConfig.Encoding = "json"
	}

	l, err := b(&zapConfig)
	if err != nil {
		return err
	}

	logger = l.WithOptions(zap.AddCallerSkip(1), zap.AddStacktrace(stackTraceLevel))
	sugar = logger.Sugar()

	// capture global zap logging and force it through our logger
	_ = zap.ReplaceGlobals(l)

	// capture standard golang "log" package output and force it through our logger
	_ = zap.RedirectStdLog(logger)

	// capture gRPC logging
	grpclog.SetLogger(zapgrpc.NewLogger(logger.WithOptions(zap.AddCallerSkip(2))))

	return nil
}

// Debug outputs a message at debug level.
// This call is a wrapper around [Logger.Debug](https://godoc.org/go.uber.org/zap#Logger.Debug)
func Debug(msg string, fields ...zapcore.Field) {
	logger.Debug(msg, fields...)
}

// Debuga uses fmt.Sprint to construct and log a message at debug level.
// This call is a wrapper around [Sugaredlogger.Debug](https://godoc.org/go.uber.org/zap#Sugaredlogger.Debug)
func Debuga(args ...interface{}) {
	sugar.Debug(args...)
}

// Debugf uses fmt.Sprintf to construct and log a message at debug level.
// This call is a wrapper around [Sugaredlogger.Debugf](https://godoc.org/go.uber.org/zap#Sugaredlogger.Debugf)
func Debugf(template string, args ...interface{}) {
	sugar.Debugf(template, args...)
}

// Debugw logs a message at debug level with some additional context.
// This call is a wrapper around [Sugaredlogger.Debugw](https://godoc.org/go.uber.org/zap#Sugaredlogger.Debugw)
func Debugw(msg string, keysAndValues ...interface{}) {
	sugar.Debugw(msg, keysAndValues...)
}

// DebugEnabled returns whether output of messages at the debug level is currently enabled.
func DebugEnabled() bool {
	return logger.Core().Enabled(zap.DebugLevel)
}

// Error outputs a message at error level.
// This call is a wrapper around [logger.Error](https://godoc.org/go.uber.org/zap#logger.Error)
func Error(msg string, fields ...zapcore.Field) {
	logger.Error(msg, fields...)
}

// Errora uses fmt.Sprint to construct and log a message at error level.
// This call is a wrapper around [Sugaredlogger.Error](https://godoc.org/go.uber.org/zap#Sugaredlogger.Error)
func Errora(args ...interface{}) {
	sugar.Error(args...)
}

// Errorf uses fmt.Sprintf to construct and log a message at error level.
// This call is a wrapper around [Sugaredlogger.Errorf](https://godoc.org/go.uber.org/zap#Sugaredlogger.Errorf)
func Errorf(template string, args ...interface{}) {
	sugar.Errorf(template, args...)
}

// Errorw logs a message at error level with some additional context.
// This call is a wrapper around [Sugaredlogger.Errorw](https://godoc.org/go.uber.org/zap#Sugaredlogger.Errorw)
func Errorw(msg string, keysAndValues ...interface{}) {
	sugar.Errorw(msg, keysAndValues...)
}

// ErrorEnabled returns whether output of messages at the error level is currently enabled.
func ErrorEnabled() bool {
	return logger.Core().Enabled(zap.ErrorLevel)
}

// Warn outputs a message at warn level.
// This call is a wrapper around [logger.Warn](https://godoc.org/go.uber.org/zap#logger.Warn)
func Warn(msg string, fields ...zapcore.Field) {
	logger.Warn(msg, fields...)
}

// Warna uses fmt.Sprint to construct and log a message at warn level.
// This call is a wrapper around [Sugaredlogger.Warn](https://godoc.org/go.uber.org/zap#Sugaredlogger.Warn)
func Warna(args ...interface{}) {
	sugar.Warn(args...)
}

// Warnf uses fmt.Sprintf to construct and log a message at warn level.
// This call is a wrapper around [Sugaredlogger.Warnf](https://godoc.org/go.uber.org/zap#Sugaredlogger.Warnf)
func Warnf(template string, args ...interface{}) {
	sugar.Warnf(template, args...)
}

// Warnw logs a message at warn level with some additional context.
// This call is a wrapper around [Sugaredlogger.Warnw](https://godoc.org/go.uber.org/zap#Sugaredlogger.Warnw)
func Warnw(msg string, keysAndValues ...interface{}) {
	sugar.Warnw(msg, keysAndValues...)
}

// WarnEnabled returns whether output of messages at the warn level is currently enabled.
func WarnEnabled() bool {
	return logger.Core().Enabled(zap.WarnLevel)
}

// Info outputs a message at information level.
// This call is a wrapper around [logger.Info](https://godoc.org/go.uber.org/zap#logger.Info)
func Info(msg string, fields ...zapcore.Field) {
	logger.Info(msg, fields...)
}

// Infoa uses fmt.Sprint to construct and log a message at info level.
// This call is a wrapper around [Sugaredlogger.Info](https://godoc.org/go.uber.org/zap#Sugaredlogger.Info)
func Infoa(args ...interface{}) {
	sugar.Info(args...)
}

// Infof uses fmt.Sprintf to construct and log a message at info level.
// This call is a wrapper around [Sugaredlogger.Infof](https://godoc.org/go.uber.org/zap#Sugaredlogger.Infof)
func Infof(template string, args ...interface{}) {
	sugar.Infof(template, args...)
}

// Infow logs a message at info level with some additional context.
// This call is a wrapper around [Sugaredlogger.Infow](https://godoc.org/go.uber.org/zap#Sugaredlogger.Infow)
func Infow(msg string, keysAndValues ...interface{}) {
	sugar.Infow(msg, keysAndValues...)
}

// InfoEnabled returns whether output of messages at the info level is currently enabled.
func InfoEnabled() bool {
	return logger.Core().Enabled(zap.InfoLevel)
}

// With creates a child logger and adds structured context to it. Fields added
// to the child don't affect the parent, and vice versa.
// This call is a wrapper around [logger.With](https://godoc.org/go.uber.org/zap#logger.With)
func With(fields ...zapcore.Field) *zap.Logger {
	return logger.With(fields...)
}

// Sync flushes any buffered log entries.
// Processes should normally take care to call Sync before exiting.
// This call is a wrapper around [logger.Sync](https://godoc.org/go.uber.org/zap#logger.Sync)
func Sync() {
	logger.Sync()
}
<|endoftext|>


<commit_before>//  ---------------------------------------------------------------------------
//
//  log.go
//
//  Copyright (c) 2015, Jared Chavez.
//  All rights reserved.
//
//  Use of this source code is governed by a BSD-style
//  license that can be found in the LICENSE file.
//
//  -----------

// Package log provides interfaces, basic types and formatting helpers
// for common application logging tasks.
package log

import (
    "container/list"
    "fmt"
    "path/filepath"
    "runtime"
    "strings"
    "sync"
    "time"
)

var timeFormat = "2006/01/02 15:04:05.000000"

// LogCloser represents the interface for a closable log object, such
// as those provided by file-backed logging.
type LogCloser interface {
    Close()
}

// Log represents the interface for a generalized log object.
type LogNotify interface {
    Print(msg string)
}

// LogToggler represents the interface needed to temporarily enabled/disable
// a given logger.
type LogToggler interface {
    SetEnabled(bool)
}

// LogBuffer holds a specified number of logs in memory for rendering
// by the http logs Uri.
type LogBuffer struct {
    changed bool
    enabled bool
    lock    sync.RWMutex
    logs    *list.List
    maxSize int
}

// NewLogBuffer returns a pointer to a new LogBuffer instance.
func NewLogBuffer(maxSize int) *LogBuffer {
    logBuffer := &LogBuffer {
        enabled : true,
        logs    : list.New(),
        maxSize : maxSize,
    }

    return logBuffer
}

// HasChanged reports whether or not the buffer has been modified
// since it was last read.
func (this *LogBuffer) HasChanged() bool {
    this.lock.RLock()
    defer this.lock.RUnlock()

    return this.changed
}

// Print formats and adds a new message to the log buffer. If the new
// message causes the log buffer to grow larger than its maxSize, it
// truncates the end oldest entry in the buffer. Once the message is
// stored, the changed flag is set to true.
func (this *LogBuffer) Print(msg string) {
    this.lock.Lock()
    defer this.lock.Unlock()

    if !this.enabled {
        return
    }

    this.logs.PushFront(msg)

    if this.logs.Len() > this.maxSize {
        this.logs.Remove(this.logs.Back())
    }

    this.changed = true
}

// ReadAll returns a list of all log messages currently in the buffer.
func (this *LogBuffer) ReadAll() []string {
    this.lock.RLock()
    defer this.lock.RUnlock()

    results := make([]string, 0, this.logs.Len())
    for e := this.logs.Front(); e != nil; e = e.Next() {
        results = append(results, e.Value.(string))
    }

    this.changed = false

    return results
}

// SetEnabled temporarily enables/disables the logger.
func (this *LogBuffer) SetEnabled(enabled bool) {
    this.lock.Lock()
    defer this.lock.Unlock()

    this.enabled = enabled
}

// SetMaxSize changes the configured maxSize for the buffer, and chops
// the oldest entries off the buffer in the case that it is currently larger
// than the newly configured maxSize.
func (this *LogBuffer) SetMaxSize(maxSize int) {
    this.lock.Lock()
    defer this.lock.Unlock()

    diff := maxSize - this.logs.Len()
    if diff < 0 {
        for i := 0; i > diff; i-- {
            this.logs.Remove(this.logs.Back())
        }
    }

    this.maxSize = maxSize
}

// FormatLogMsg formats a log given the standard logging format
// yyyy/mm/dd MM:HH:SS.ssssss [NAME] <file:line> msg
func FormatLogMsg(name, format string, callDepth int, v ...interface{}) string {
    var msg string
    if len(v) < 1 {
        msg = format
    } else {
        msg = fmt.Sprintf(format, v...)
    }

    _, file, line, ok := runtime.Caller(callDepth)
    if ok {
        file = fmt.Sprintf(" <%s:%d>", filepath.Base(file), line)
    }

    if msg[len(msg) - 1] == '\n' {
        return fmt.Sprintf(
            "%s [%s]%s %s",
            time.Now().Format(timeFormat),
            strings.ToUpper(name),
            file,
            msg,
        )
    }

    return fmt.Sprintf(
        "%s [%s]%s %s\n",
        time.Now().Format(timeFormat),
        strings.ToUpper(name),
        file,
        msg,
    )
}
<commit_msg>add json marshaler for LogBuffer objects<commit_after>//  ---------------------------------------------------------------------------
//
//  log.go
//
//  Copyright (c) 2015, Jared Chavez.
//  All rights reserved.
//
//  Use of this source code is governed by a BSD-style
//  license that can be found in the LICENSE file.
//
//  -----------

// Package log provides interfaces, basic types and formatting helpers
// for common application logging tasks.
package log

import (
    "bytes"
    "container/list"
    "fmt"
    "path/filepath"
    "runtime"
    "strings"
    "sync"
    "time"
)

var timeFormat = "2006/01/02 15:04:05.000000"

// LogCloser represents the interface for a closable log object, such
// as those provided by file-backed logging.
type LogCloser interface {
    Close()
}

// Log represents the interface for a generalized log object.
type LogNotify interface {
    Print(msg string)
}

// LogToggler represents the interface needed to temporarily enabled/disable
// a given logger.
type LogToggler interface {
    SetEnabled(bool)
}

// LogBuffer holds a specified number of logs in memory for rendering
// by the http logs Uri.
type LogBuffer struct {
    changed bool
    enabled bool
    lock    sync.RWMutex
    logs    *list.List
    maxSize int
}

// NewLogBuffer returns a pointer to a new LogBuffer instance.
func NewLogBuffer(maxSize int) *LogBuffer {
    logBuffer := &LogBuffer {
        enabled : true,
        logs    : list.New(),
        maxSize : maxSize,
    }

    return logBuffer
}

// HasChanged reports whether or not the buffer has been modified
// since it was last read.
func (this *LogBuffer) HasChanged() bool {
    this.lock.RLock()
    defer this.lock.RUnlock()

    return this.changed
}

// Print formats and adds a new message to the log buffer. If the new
// message causes the log buffer to grow larger than its maxSize, it
// truncates the end oldest entry in the buffer. Once the message is
// stored, the changed flag is set to true.
func (this *LogBuffer) Print(msg string) {
    this.lock.Lock()
    defer this.lock.Unlock()

    if !this.enabled {
        return
    }

    this.logs.PushFront(msg)

    if this.logs.Len() > this.maxSize {
        this.logs.Remove(this.logs.Back())
    }

    this.changed = true
}

// ReadAll returns a list of all log messages currently in the buffer.
func (this *LogBuffer) ReadAll() []string {
    this.lock.RLock()
    defer this.lock.RUnlock()

    results := make([]string, 0, this.logs.Len())
    for e := this.logs.Front(); e != nil; e = e.Next() {
        results = append(results, e.Value.(string))
    }

    this.changed = false

    return results
}

// MarshalJSON implements the standard go json marshaler for the
// LogBuffer type.
func (this *LogBuffer) MarshalJSON() ([]byte, error) {
    this.lock.RLock()
    defer this.lock.RUnlock()

    var buffer bytes.Buffer

    buffer.WriteString("[")
    for e := this.logs.Front(); e != nil; {
        buffer.WriteString("\"")
        buffer.WriteString(strings.TrimSpace(e.Value.(string)))
        buffer.WriteString("\"")

        next := e.Next()
        if next == nil {
            break
        }

        buffer.WriteString(",")
        e = next
    }
    buffer.WriteString("]")

    return buffer.Bytes(), nil
}

// SetEnabled temporarily enables/disables the logger.
func (this *LogBuffer) SetEnabled(enabled bool) {
    this.lock.Lock()
    defer this.lock.Unlock()

    this.enabled = enabled
}

// SetMaxSize changes the configured maxSize for the buffer, and chops
// the oldest entries off the buffer in the case that it is currently larger
// than the newly configured maxSize.
func (this *LogBuffer) SetMaxSize(maxSize int) {
    this.lock.Lock()
    defer this.lock.Unlock()

    diff := maxSize - this.logs.Len()
    if diff < 0 {
        for i := 0; i > diff; i-- {
            this.logs.Remove(this.logs.Back())
        }
    }

    this.maxSize = maxSize
}

// FormatLogMsg formats a log given the standard logging format
// yyyy/mm/dd MM:HH:SS.ssssss [NAME] <file:line> msg
func FormatLogMsg(name, format string, callDepth int, v ...interface{}) string {
    var msg string
    if len(v) < 1 {
        msg = format
    } else {
        msg = fmt.Sprintf(format, v...)
    }

    _, file, line, ok := runtime.Caller(callDepth)
    if ok {
        file = fmt.Sprintf(" <%s:%d>", filepath.Base(file), line)
    }

    if len(msg) < 1 {
        return ""
    }

    if msg[len(msg) - 1] == '\n' {
        return fmt.Sprintf(
            "%s [%s]%s %s",
            time.Now().Format(timeFormat),
            strings.ToUpper(name),
            file,
            msg,
        )
    }

    return fmt.Sprintf(
        "%s [%s]%s %s\n",
        time.Now().Format(timeFormat),
        strings.ToUpper(name),
        file,
        msg,
    )
}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Knative Authors

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 mysql

import (
	"database/sql"
	"time"

	"github.com/knative/test-infra/shared/mysql"
	"github.com/knative/test-infra/tools/monitoring/config"
	"github.com/knative/test-infra/tools/monitoring/log_parser"
)

const (
	insertStmt = `
	INSERT INTO ErrorLogs (
		ErrorPattern, ErrorMsg, JobName, PRNumber, BuildLogURL, TimeStamp
		) VALUES (?,?,?,?,?,?)`
)

// PubsubMsgHandler adds record(s) to ErrorLogs table in database,
// after parsing build log and compares the result with config yaml
func PubsubMsgHandler(db *sql.DB, configURL, buildLogURL, jobname string, prNumber int) error {
	config, err := config.ParseYaml(configURL)
	if err != nil {
		return err
	}

	errorLogs, err := log_parser.ParseLog(buildLogURL, config.CollectErrorPatterns())
	if err != nil {
		return err
	}

	tx, err := db.Begin()
	if err != nil {
		return err
	}

	stmt, err := tx.Prepare(insertStmt)
	defer stmt.Close()

	if err != nil {
		return mysql.RollbackTx(tx, err)
	}

	for _, errorLog := range errorLogs {
		if _, err := stmt.Exec(errorLog.Pattern, errorLog.Msg, jobname, prNumber, buildLogURL, time.Now()); err != nil {
			return mysql.RollbackTx(tx, err)
		}
	}

	return tx.Commit()
}

// CheckAlertCondition checks whether the given error pattern meets
// the alert condition specified in config
func CheckAlertCondition(errorPattern string, config *config.SelectedConfig, db *sql.DB) (bool, error) {
	// the timestamp we want to start collecting logs
	startTime := time.Now().Add(time.Minute * time.Duration(config.Period))

	var nMatches, nJobs, nPRs int

	row := db.QueryRow(`
		SELECT
			COUNT(*),
			COUNT (DISTINCT Jobname),
			COUNT (DISTINCT PrNumber)
		FROM ErrorLogs
		WHERE ErrorPattern=? and TimeStamp > ?`,
		errorPattern, startTime)

	if err := row.Scan(&nMatches, &nJobs, &nPRs); err != nil {
		return false, err
	}

	return nMatches >= config.Occurrences && nJobs >= config.JobsAffected && nPRs >= config.PrsAffected, nil
}
<commit_msg>convert Time in golang to sql timestamp format (#907)<commit_after>/*
Copyright 2019 The Knative Authors

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 mysql

import (
	"database/sql"
	"time"

	"github.com/knative/test-infra/shared/mysql"
	"github.com/knative/test-infra/tools/monitoring/config"
	"github.com/knative/test-infra/tools/monitoring/log_parser"
)

const (
	sqlTimestampLayout = "2019-01-02 15:03:04"
	insertStmt         = `
	INSERT INTO ErrorLogs (
		ErrorPattern, ErrorMsg, JobName, PRNumber, BuildLogURL, TimeStamp
		) VALUES (?,?,?,?,?,?)`
)

// PubsubMsgHandler adds record(s) to ErrorLogs table in database,
// after parsing build log and compares the result with config yaml
func PubsubMsgHandler(db *sql.DB, configURL, buildLogURL, jobname string, prNumber int) error {
	config, err := config.ParseYaml(configURL)
	if err != nil {
		return err
	}

	errorLogs, err := log_parser.ParseLog(buildLogURL, config.CollectErrorPatterns())
	if err != nil {
		return err
	}

	tx, err := db.Begin()
	if err != nil {
		return err
	}

	stmt, err := tx.Prepare(insertStmt)
	defer stmt.Close()

	if err != nil {
		return mysql.RollbackTx(tx, err)
	}

	for _, errorLog := range errorLogs {
		if _, err := stmt.Exec(errorLog.Pattern, errorLog.Msg, jobname, prNumber, buildLogURL, time.Now()); err != nil {
			return mysql.RollbackTx(tx, err)
		}
	}

	return tx.Commit()
}

// CheckAlertCondition checks whether the given error pattern meets
// the alert condition specified in config
func CheckAlertCondition(errorPattern string, config *config.SelectedConfig, db *sql.DB) (bool, error) {
	// the timestamp we want to start collecting logs
	startTime := time.Now().Add(time.Minute * time.Duration(config.Period))

	var nMatches, nJobs, nPRs int

	row := db.QueryRow(`
		SELECT
			COUNT(*),
			COUNT (DISTINCT Jobname),
			COUNT (DISTINCT PrNumber)
		FROM ErrorLogs
		WHERE ErrorPattern=? and TimeStamp > ?`,
		errorPattern, startTime.Format(sqlTimestampLayout))

	if err := row.Scan(&nMatches, &nJobs, &nPRs); err != nil {
		return false, err
	}

	return nMatches >= config.Occurrences && nJobs >= config.JobsAffected && nPRs >= config.PrsAffected, nil
}
<|endoftext|>


<commit_before>// Copyright 2016 Giulio Iotti. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"fmt"
	"io"
	"log"
	"net"
	"strings"
	"sync"
	"time"

	"github.com/miekg/dns"

	"github.com/dullgiulio/kuradns/gen"
)

// A record containse the source and destination from the generator,
// a precomputed A and CNAME record and a pointer to the source that
// generated this entry.
type record struct {
	shost, dhost host
	a            dns.RR
	cname        dns.RR
	source       *source
}

// Allocate a new record. For the A record, a resoled IP is needed.
func makeRecord(shost, dhost host, cname bool, ip net.IP, ttl time.Duration, src *source) record {
	r := record{
		shost: shost,
		dhost: dhost,
		a: &dns.A{
			Hdr: dns.RR_Header{
				Name:   shost.dns(),
				Rrtype: dns.TypeA,
				Class:  dns.ClassINET,
				Ttl:    uint32(ttl.Seconds()),
			},
			A: ip,
		},
		source: src,
	}
	if cname {
		r.cname = &dns.CNAME{
			Hdr: dns.RR_Header{
				Name:   shost.dns(),
				Rrtype: dns.TypeCNAME,
				Class:  dns.ClassINET,
				Ttl:    uint32(ttl.Seconds()),
			},
			Target: host(dhost).dns(),
		}
	}
	return r
}

// target returns the human DNS representation of the destination/target or a record.
func (r record) target() string {
	return r.dhost.browser()
}

// String representation of a record.
func (r record) String() string {
	return fmt.Sprintf("[%s %s]", r.source.String(), r.shost.dns())
}

// Collection of records.
type records struct {
	recs []record
}

// Allocate a new collection of records.
func newRecords() *records {
	return &records{recs: make([]record, 0)}
}

// String representation of a collection of records.
func (r *records) String() string {
	return fmt.Sprintf("%s", r.recs)
}

// deleteSource removes all record that were added by source s.
func (r *records) deleteSource(s *source) {
	res := r.recs[:0]
	for _, rec := range r.recs {
		if rec.source.name != s.name {
			res = append(res, rec)
		}
	}
	r.recs = res
}

// pushFront adds a record to the collection.
func (r *records) pushFront(rec record) {
	r.recs = append([]record{rec}, r.recs...)
}

// clone is the utility function to duplicate a collection.
func (r *records) clone() *records {
	nr := &records{
		recs: make([]record, len(r.recs)),
	}
	for i := 0; i < len(r.recs); i++ {
		nr.recs[i] = r.recs[i]
	}
	return nr
}

// A host is a FQDN or common representation of a DNS address.
type host string

// browser returns the FQDN without the trailing dot.
func (h host) browser() string {
	return strings.TrimSuffix(string(h), ".")
}

// dns returns the FQDN.
func (h host) dns() string {
	return dns.Fqdn(string(h))
}

// hasSuffix returns true if h has suffix h2.
func (h host) hasSuffix(h2 host) bool {
	return strings.HasSuffix(h.browser(), h2.browser())
}

// A repository maps hosts to the records that can resolve them (record collection).
// repository is not thread safe.
type repository map[string]*records

// makeRepository allocates a new repository.
func makeRepository() repository {
	return make(map[string]*records)
}

// add inserts record rec for host as the default record.
func (r repository) add(host host, rec record) {
	key := host.browser()
	recs, ok := r[key]
	if !ok {
		recs = newRecords()
	}
	recs.pushFront(rec)
	r[key] = recs
}

// deleteSource removes all records that were inserted by source s.
func (r repository) deleteSource(s *source) {
	for _, recs := range r {
		recs.deleteSource(s)
	}
}

// updateSource removes and generate again all records for source src.
func (r repository) updateSource(src *source, zone host, ttl time.Duration) {
	res := newResolver(src, ttl, 6)

	go func() {
		for {
			rentry := src.gen.Generate()
			if rentry.IsEmpty() {
				close(res.rentries)
				return
			}
			src := host(rentry.Source)
			if !src.hasSuffix(zone) {
				log.Printf("[error] repository: domain %s is not inside zone %s, skipped", src.dns(), zone.dns())
				continue
			}
			res.rentries <- rentry
		}
	}()

	for rec := range res.records {
		r.add(rec.shost, rec)
	}
}

// resolver is a worker that resolves strings into IPs.
type resolver struct {
	cname    bool
	src      *source
	ttl      time.Duration
	rentries chan gen.RawEntry
	records  chan record
	wg       sync.WaitGroup
}

// Allocate a new resolver. Subsequent records will be generated with ttl set as given here.
// workers is number of workers to be run in parallel.
func newResolver(src *source, ttl time.Duration, workers int) *resolver {
	r := &resolver{
		src:      src,
		ttl:      ttl,
		rentries: make(chan gen.RawEntry),
		records:  make(chan record),
	}
	r.wg.Add(workers)
	for i := 0; i < workers; i++ {
		go r.run()
	}
	go func() {
		r.wg.Wait()
		close(r.records)
	}()
	return r
}

// run resolves incoming entries and emits records. It is called automatically.
func (r *resolver) run() {
	for rentry := range r.rentries {
		var cname bool
		ip := net.ParseIP(rentry.Target)
		if ip == nil {
			var err error
			// It's an hostname: resolve it and make both A and CNAME records
			ip, err = lookup(rentry.Target)
			if err != nil {
				log.Printf("[error] repository: failed lookup of %s: %s", rentry.Target, err)
				continue
			}
			cname = true
		}
		r.records <- makeRecord(host(rentry.Source), host(rentry.Target), cname, ip, r.ttl, r.src)
	}
	r.wg.Done()
}

// lookup is a utility to lookup an IP for host (standard format).
func lookup(host string) (net.IP, error) {
	var ip net.IP
	iplist, err := net.LookupIP(host)
	if err == nil {
		ip = iplist[0]
	}
	return ip, err
}

// get returns the default record for host or nil if not found.
func (r repository) get(host host) *record {
	rs, ok := r[host.browser()]
	if !ok {
		return nil
	}
	return &rs.recs[0]
}

// clone duplicates the whole repository.
func (r repository) clone() repository {
	nr := makeRepository()
	for k, recs := range r {
		nr[k] = recs.clone()
	}
	return nr
}

// WriteTo writes the repository contents in hosts format to w.
func (r repository) WriteTo(w io.Writer) error {
	for key, rs := range r {
		if len(rs.recs) == 0 {
			continue
		}
		// TODO: Display IP not aliased target.
		if _, err := fmt.Fprintf(w, "%s\t%s\n", rs.recs[0].target(), key); err != nil {
			return err
		}
		for i := 1; i < len(rs.recs); i++ {
			if _, err := fmt.Fprintf(w, "# %s\t%s\n", rs.recs[i].target(), key); err != nil {
				return err
			}
		}
	}
	return nil
}
<commit_msg>go fmt<commit_after>// Copyright 2016 Giulio Iotti. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"fmt"
	"io"
	"log"
	"net"
	"strings"
	"sync"
	"time"

	"github.com/miekg/dns"

	"github.com/dullgiulio/kuradns/gen"
)

// A record containse the source and destination from the generator,
// a precomputed A and CNAME record and a pointer to the source that
// generated this entry.
type record struct {
	shost, dhost host
	a            dns.RR
	cname        dns.RR
	source       *source
}

// Allocate a new record. For the A record, a resoled IP is needed.
func makeRecord(shost, dhost host, cname bool, ip net.IP, ttl time.Duration, src *source) record {
	r := record{
		shost: shost,
		dhost: dhost,
		a: &dns.A{
			Hdr: dns.RR_Header{
				Name:   shost.dns(),
				Rrtype: dns.TypeA,
				Class:  dns.ClassINET,
				Ttl:    uint32(ttl.Seconds()),
			},
			A: ip,
		},
		source: src,
	}
	if cname {
		r.cname = &dns.CNAME{
			Hdr: dns.RR_Header{
				Name:   shost.dns(),
				Rrtype: dns.TypeCNAME,
				Class:  dns.ClassINET,
				Ttl:    uint32(ttl.Seconds()),
			},
			Target: host(dhost).dns(),
		}
	}
	return r
}

// target returns the human DNS representation of the destination/target or a record.
func (r record) target() string {
	return r.dhost.browser()
}

// String representation of a record.
func (r record) String() string {
	return fmt.Sprintf("[%s %s]", r.source.String(), r.shost.dns())
}

// Collection of records.
type records struct {
	recs []record
}

// Allocate a new collection of records.
func newRecords() *records {
	return &records{recs: make([]record, 0)}
}

// String representation of a collection of records.
func (r *records) String() string {
	return fmt.Sprintf("%s", r.recs)
}

// deleteSource removes all record that were added by source s.
func (r *records) deleteSource(s *source) {
	res := r.recs[:0]
	for _, rec := range r.recs {
		if rec.source.name != s.name {
			res = append(res, rec)
		}
	}
	r.recs = res
}

// pushFront adds a record to the collection.
func (r *records) pushFront(rec record) {
	r.recs = append([]record{rec}, r.recs...)
}

// clone is the utility function to duplicate a collection.
func (r *records) clone() *records {
	nr := &records{
		recs: make([]record, len(r.recs)),
	}
	for i := 0; i < len(r.recs); i++ {
		nr.recs[i] = r.recs[i]
	}
	return nr
}

// A host is a FQDN or common representation of a DNS address.
type host string

// browser returns the FQDN without the trailing dot.
func (h host) browser() string {
	return strings.TrimSuffix(string(h), ".")
}

// dns returns the FQDN.
func (h host) dns() string {
	return dns.Fqdn(string(h))
}

// hasSuffix returns true if h has suffix h2.
func (h host) hasSuffix(h2 host) bool {
	return strings.HasSuffix(h.browser(), h2.browser())
}

// A repository maps hosts to the records that can resolve them (record collection).
// repository is not thread safe.
type repository map[string]*records

// makeRepository allocates a new repository.
func makeRepository() repository {
	return make(map[string]*records)
}

// add inserts record rec for host as the default record.
func (r repository) add(host host, rec record) {
	key := host.browser()
	recs, ok := r[key]
	if !ok {
		recs = newRecords()
	}
	recs.pushFront(rec)
	r[key] = recs
}

// deleteSource removes all records that were inserted by source s.
func (r repository) deleteSource(s *source) {
	for _, recs := range r {
		recs.deleteSource(s)
	}
}

// updateSource removes and generate again all records for source src.
func (r repository) updateSource(src *source, zone host, ttl time.Duration) {
	res := newResolver(src, ttl, 6)

	go func() {
		for {
			rentry := src.gen.Generate()
			if rentry.IsEmpty() {
				close(res.rentries)
				return
			}
			src := host(rentry.Source)
			if !src.hasSuffix(zone) {
				log.Printf("[error] repository: domain %s is not inside zone %s, skipped", src.dns(), zone.dns())
				continue
			}
			res.rentries <- rentry
		}
	}()

	for rec := range res.records {
		r.add(rec.shost, rec)
	}
}

// resolver is a worker that resolves strings into IPs.
type resolver struct {
	cname    bool
	src      *source
	ttl      time.Duration
	rentries chan gen.RawEntry
	records  chan record
	wg       sync.WaitGroup
}

// Allocate a new resolver. Subsequent records will be generated with ttl set as given here.
// workers is number of workers to be run in parallel.
func newResolver(src *source, ttl time.Duration, workers int) *resolver {
	r := &resolver{
		src:      src,
		ttl:      ttl,
		rentries: make(chan gen.RawEntry),
		records:  make(chan record),
	}
	r.wg.Add(workers)
	for i := 0; i < workers; i++ {
		go r.run()
	}
	go func() {
		r.wg.Wait()
		close(r.records)
	}()
	return r
}

// run resolves incoming entries and emits records. It is called automatically.
func (r *resolver) run() {
	for rentry := range r.rentries {
		var cname bool
		ip := net.ParseIP(rentry.Target)
		if ip == nil {
			var err error
			// It's an hostname: resolve it and make both A and CNAME records
			ip, err = lookup(rentry.Target)
			if err != nil {
				log.Printf("[error] repository: failed lookup of %s: %s", rentry.Target, err)
				continue
			}
			cname = true
		}
		r.records <- makeRecord(host(rentry.Source), host(rentry.Target), cname, ip, r.ttl, r.src)
	}
	r.wg.Done()
}

// lookup is a utility to lookup an IP for host (standard format).
func lookup(host string) (net.IP, error) {
	var ip net.IP
	iplist, err := net.LookupIP(host)
	if err == nil {
		ip = iplist[0]
	}
	return ip, err
}

// get returns the default record for host or nil if not found.
func (r repository) get(host host) *record {
	rs, ok := r[host.browser()]
	if !ok {
		return nil
	}
	return &rs.recs[0]
}

// clone duplicates the whole repository.
func (r repository) clone() repository {
	nr := makeRepository()
	for k, recs := range r {
		nr[k] = recs.clone()
	}
	return nr
}

// WriteTo writes the repository contents in hosts format to w.
func (r repository) WriteTo(w io.Writer) error {
	for key, rs := range r {
		if len(rs.recs) == 0 {
			continue
		}
		// TODO: Display IP not aliased target.
		if _, err := fmt.Fprintf(w, "%s\t%s\n", rs.recs[0].target(), key); err != nil {
			return err
		}
		for i := 1; i < len(rs.recs); i++ {
			if _, err := fmt.Fprintf(w, "# %s\t%s\n", rs.recs[i].target(), key); err != nil {
				return err
			}
		}
	}
	return nil
}
<|endoftext|>


<commit_before>// This package provides a simple LRU cache. It is based on the
// LRU implementation in groupcache:
// https://github.com/golang/groupcache/tree/master/lru
package lru

import (
	"container/list"
	"errors"
	"sync"
)

// Cache is a thread-safe fixed size LRU cache.
type Cache struct {
	size      int
	evictList *list.List
	items     map[interface{}]*list.Element
	lock      sync.RWMutex
	onEvicted func(key interface{}, value interface{})
}

// entry is used to hold a value in the evictList
type entry struct {
	key   interface{}
	value interface{}
}

// New creates an LRU of the given size
func New(size int) (*Cache, error) {
	return NewWithEvict(size, nil)
}

func NewWithEvict(size int, onEvicted func(key interface{}, value interface{})) (*Cache, error) {
	if size <= 0 {
		return nil, errors.New("Must provide a positive size")
	}
	c := &Cache{
		size:      size,
		evictList: list.New(),
		items:     make(map[interface{}]*list.Element, size),
		onEvicted: onEvicted,
	}
	return c, nil
}

// Purge is used to completely clear the cache
func (c *Cache) Purge() {
	c.lock.Lock()
	defer c.lock.Unlock()

	e := c.evictList.Back()
	for e != nil {
		n := e.Prev()
		c.removeElement(e)
		e = n
	}
}

// Add adds a value to the cache.
func (c *Cache) Add(key, value interface{}) {
	c.lock.Lock()
	defer c.lock.Unlock()

	// Check for existing item
	if ent, ok := c.items[key]; ok {
		c.evictList.MoveToFront(ent)
		ent.Value.(*entry).value = value
		return
	}

	// Add new item
	ent := &entry{key, value}
	entry := c.evictList.PushFront(ent)
	c.items[key] = entry

	// Verify size not exceeded
	if c.evictList.Len() > c.size {
		c.removeOldest()
	}
}

// Get looks up a key's value from the cache.
func (c *Cache) Get(key interface{}) (value interface{}, ok bool) {
	c.lock.Lock()
	defer c.lock.Unlock()

	if ent, ok := c.items[key]; ok {
		c.evictList.MoveToFront(ent)
		return ent.Value.(*entry).value, true
	}
	return
}

// Remove removes the provided key from the cache.
func (c *Cache) Remove(key interface{}) {
	c.lock.Lock()
	defer c.lock.Unlock()

	if ent, ok := c.items[key]; ok {
		c.removeElement(ent)
	}
}

// RemoveOldest removes the oldest item from the cache.
func (c *Cache) RemoveOldest() {
	c.lock.Lock()
	defer c.lock.Unlock()
	c.removeOldest()
}

// Keys returns a slice of the keys in the cache.
func (c *Cache) Keys() []interface{} {
	c.lock.RLock()
	defer c.lock.RUnlock()

	keys := make([]interface{}, len(c.items))
	i := 0
	for k := range c.items {
		keys[i] = k
		i++
	}

	return keys
}

// Len returns the number of items in the cache.
func (c *Cache) Len() int {
	c.lock.RLock()
	defer c.lock.RUnlock()
	return c.evictList.Len()
}

// removeOldest removes the oldest item from the cache.
func (c *Cache) removeOldest() {
	ent := c.evictList.Back()
	if ent != nil {
		c.removeElement(ent)
	}
}

// removeElement is used to remove a given list element from the cache
func (c *Cache) removeElement(e *list.Element) {
	c.evictList.Remove(e)
	kv := e.Value.(*entry)
	delete(c.items, kv.key)
	if c.onEvicted != nil {
		c.onEvicted(kv.key, kv.value)
	}
}
<commit_msg>Call the evict function, then reset the cache.<commit_after>// This package provides a simple LRU cache. It is based on the
// LRU implementation in groupcache:
// https://github.com/golang/groupcache/tree/master/lru
package lru

import (
	"container/list"
	"errors"
	"sync"
)

// Cache is a thread-safe fixed size LRU cache.
type Cache struct {
	size      int
	evictList *list.List
	items     map[interface{}]*list.Element
	lock      sync.RWMutex
	onEvicted func(key interface{}, value interface{})
}

// entry is used to hold a value in the evictList
type entry struct {
	key   interface{}
	value interface{}
}

// New creates an LRU of the given size
func New(size int) (*Cache, error) {
	return NewWithEvict(size, nil)
}

func NewWithEvict(size int, onEvicted func(key interface{}, value interface{})) (*Cache, error) {
	if size <= 0 {
		return nil, errors.New("Must provide a positive size")
	}
	c := &Cache{
		size:      size,
		evictList: list.New(),
		items:     make(map[interface{}]*list.Element, size),
		onEvicted: onEvicted,
	}
	return c, nil
}

// Purge is used to completely clear the cache
func (c *Cache) Purge() {
	c.lock.Lock()
	defer c.lock.Unlock()

	if c.onEvicted != nil {
		for k, v := range c.items {
			c.onEvicted(k, v.Value)
		}
	}

	c.evictList = list.New()
	c.items = make(map[interface{}]*list.Element, c.size)
}

// Add adds a value to the cache.
func (c *Cache) Add(key, value interface{}) {
	c.lock.Lock()
	defer c.lock.Unlock()

	// Check for existing item
	if ent, ok := c.items[key]; ok {
		c.evictList.MoveToFront(ent)
		ent.Value.(*entry).value = value
		return
	}

	// Add new item
	ent := &entry{key, value}
	entry := c.evictList.PushFront(ent)
	c.items[key] = entry

	// Verify size not exceeded
	if c.evictList.Len() > c.size {
		c.removeOldest()
	}
}

// Get looks up a key's value from the cache.
func (c *Cache) Get(key interface{}) (value interface{}, ok bool) {
	c.lock.Lock()
	defer c.lock.Unlock()

	if ent, ok := c.items[key]; ok {
		c.evictList.MoveToFront(ent)
		return ent.Value.(*entry).value, true
	}
	return
}

// Remove removes the provided key from the cache.
func (c *Cache) Remove(key interface{}) {
	c.lock.Lock()
	defer c.lock.Unlock()

	if ent, ok := c.items[key]; ok {
		c.removeElement(ent)
	}
}

// RemoveOldest removes the oldest item from the cache.
func (c *Cache) RemoveOldest() {
	c.lock.Lock()
	defer c.lock.Unlock()
	c.removeOldest()
}

// Keys returns a slice of the keys in the cache.
func (c *Cache) Keys() []interface{} {
	c.lock.RLock()
	defer c.lock.RUnlock()

	keys := make([]interface{}, len(c.items))
	i := 0
	for k := range c.items {
		keys[i] = k
		i++
	}

	return keys
}

// Len returns the number of items in the cache.
func (c *Cache) Len() int {
	c.lock.RLock()
	defer c.lock.RUnlock()
	return c.evictList.Len()
}

// removeOldest removes the oldest item from the cache.
func (c *Cache) removeOldest() {
	ent := c.evictList.Back()
	if ent != nil {
		c.removeElement(ent)
	}
}

// removeElement is used to remove a given list element from the cache
func (c *Cache) removeElement(e *list.Element) {
	c.evictList.Remove(e)
	kv := e.Value.(*entry)
	delete(c.items, kv.key)
	if c.onEvicted != nil {
		c.onEvicted(kv.key, kv.value)
	}
}
<|endoftext|>


<commit_before>package tmsh

import (
	"fmt"
	"strconv"
	"strings"
)

type Node struct {
	Name          string
	Addr          string
	MonitorRule   string
	MonitorStatus string
	EnabledState  string
}

type Pool struct {
	ActiveMemberCount int
	Name              string
	MonitorRule       string
	AvailabilityState string
	EnabledState      string
	StatusReason      string
	PoolMembers       []PoolMember
}

type PoolMember struct {
	Name              string
	Addr              string
	Port              int
	MonitorRule       string
	MonitorStatus     string
	EnabledState      string
	AvailabilityState string
	StatusReason      string
}

type VirtualServer struct {
	Destination string
	IpProtocol  string
	Mask        string
	Partition   string
	Pool        string
}

func (bigip *BigIP) GetNode(name string) (*Node, error) {
	ret, _ := bigip.ExecuteCommand("show ltm node " + name + " field-fmt")
	if strings.Contains(ret, "was not found.") {
		return nil, fmt.Errorf(ret)
	}
	node := ParseShowLtmNode(ret)
	return node, nil
}

func (bigip *BigIP) CreateNode(name, ipaddr string) error {
	ret, _ := bigip.ExecuteCommand("create ltm node " + name + " address " + ipaddr)
	if ret != "" {
		return fmt.Errorf(ret)
	}
	return nil
}

func (bigip *BigIP) DeleteNode(name string) error {
	ret, _ := bigip.ExecuteCommand("delete ltm node " + name)
	if ret != "" {
		return fmt.Errorf(ret)
	}
	return nil
}

func (bigip *BigIP) EnableNode(name string) error {
	ret, _ := bigip.ExecuteCommand("modify ltm node " + name + " session user-enabled")
	if ret != "" {
		return fmt.Errorf(ret)
	}
	return nil
}

func (bigip *BigIP) DisableNode(name string) error {
	ret, _ := bigip.ExecuteCommand("modify ltm node " + name + " session user-disabled")
	if ret != "" {
		return fmt.Errorf(ret)
	}
	return nil
}

func (bigip *BigIP) GetPool(name string) (*Pool, error) {
	ret, _ := bigip.ExecuteCommand("show ltm pool " + name + " members field-fmt")
	if strings.Contains(ret, "was not found.") {
		return nil, fmt.Errorf(ret)
	}
	pool := ParseShowLtmPool(ret)
	return pool, nil
}

func (bigip *BigIP) CreatePool(name string) error {
	ret, _ := bigip.ExecuteCommand("create ltm pool " + name)
	if ret != "" {
		return fmt.Errorf(ret)
	}
	return nil
}

func (bigip *BigIP) DeletePool(name string) error {
	ret, _ := bigip.ExecuteCommand("delete ltm pool " + name)
	if ret != "" {
		return fmt.Errorf(ret)
	}
	return nil
}

func (bigip *BigIP) AddMonitorToPool(poolName, monitorName string) error {
	ret, _ := bigip.ExecuteCommand("modify ltm pool " + poolName + " monitor '" + monitorName + "'")
	if ret != "" {
		return fmt.Errorf(ret)
	}
	return nil
}

func (bigip *BigIP) AddPoolMember(poolName, nodeName, monitorName string, port int) error {
	member := nodeName + ":" + strconv.Itoa(port)
	cmd := "modify ltm pool " + poolName + " members add { " + member + " } monitor '" + monitorName + "'"
	ret, _ := bigip.ExecuteCommand(cmd)
	if ret != "" {
		return fmt.Errorf(ret)
	}
	return nil
}

func (bigip *BigIP) DeletePoolMember(poolName, nodeName string, port int) error {
	member := nodeName + ":" + strconv.Itoa(port)
	cmd := "modify ltm pool " + poolName + " members delete { " + member + " }"
	ret, _ := bigip.ExecuteCommand(cmd)
	if ret != "" {
		return fmt.Errorf(ret)
	}
	return nil
}

func (bigip *BigIP) EnablePoolMember(poolName, nodeName string, port int) error {
	member := nodeName + ":" + strconv.Itoa(port)
	cmd := "modify ltm pool " + poolName + " members modify { " + member + " { session user-enabled } }"
	ret, _ := bigip.ExecuteCommand(cmd)
	if ret != "" {
		return fmt.Errorf(ret)
	}
	return nil
}

func (bigip *BigIP) DisablePoolMember(poolName, nodeName string, port int) error {
	member := nodeName + ":" + strconv.Itoa(port)
	cmd := "modify ltm pool " + poolName + " members modify { " + member + " { session user-disabled } }"
	ret, _ := bigip.ExecuteCommand(cmd)
	if ret != "" {
		return fmt.Errorf(ret)
	}
	return nil
}

func (bigip *BigIP) GetVirtualServer(name string) (*VirtualServer, error) {
	ret, _ := bigip.ExecuteCommand("list ltm virtual " + name)
	if strings.Contains(ret, "was not found.") {
		return nil, fmt.Errorf(ret)
	}
	vs := ParseListLtmVirtual(ret)
	return vs, nil

}

func (bigip *BigIP) CreateVirtualServer(vsName, poolName, targetVIP, defaultProfileName string, targetPort int) error {
	destination := targetVIP + ":" + strconv.Itoa(targetPort)
	cmd := "create ltm virtual " + vsName + " { destination " + destination + " ip-protocol tcp mask 255.255.255.255 pool " + poolName + " profiles add { " + defaultProfileName + " } }"
	ret, _ := bigip.ExecuteCommand(cmd)
	if ret != "" {
		return fmt.Errorf(ret)
	}
	return nil
}

func (bigip *BigIP) DeleteVirtualServer(vsName string) error {
	ret, _ := bigip.ExecuteCommand("delete ltm virtual " + vsName)
	if ret != "" {
		return fmt.Errorf(ret)
	}
	return nil
}

func (bigip *BigIP) AddVirtualServerProfile(vsName, profileName, context string) error {
	cmd := "modify ltm virtual " + vsName + " profiles add { " + profileName + " { context " + context + " } }"
	ret, _ := bigip.ExecuteCommand(cmd)
	if ret != "" {
		return fmt.Errorf(ret)
	}
	return nil
}

func (bigip *BigIP) DeleteVirtualServerProfile(vsName, profileName, context string) error {
	cmd := "modify ltm virtual " + vsName + " profiles delete { " + profileName + " }"
	ret, _ := bigip.ExecuteCommand(cmd)
	if ret != "" {
		return fmt.Errorf(ret)
	}
	return nil
}

func (bigip *BigIP) ApplyPolicyToVirtualServer(vsName, policyName) error {
	cmd := "modify ltm virtual " + vsName + " fw-enforced-policy " + policyName
	ret, _ := bigip.ExecuteCommand(cmd)
	if ret != "" {
		return fmt.Errorf(ret)
	}
	return nil
}
<commit_msg>Fix argument type<commit_after>package tmsh

import (
	"fmt"
	"strconv"
	"strings"
)

type Node struct {
	Name          string
	Addr          string
	MonitorRule   string
	MonitorStatus string
	EnabledState  string
}

type Pool struct {
	ActiveMemberCount int
	Name              string
	MonitorRule       string
	AvailabilityState string
	EnabledState      string
	StatusReason      string
	PoolMembers       []PoolMember
}

type PoolMember struct {
	Name              string
	Addr              string
	Port              int
	MonitorRule       string
	MonitorStatus     string
	EnabledState      string
	AvailabilityState string
	StatusReason      string
}

type VirtualServer struct {
	Destination string
	IpProtocol  string
	Mask        string
	Partition   string
	Pool        string
}

func (bigip *BigIP) GetNode(name string) (*Node, error) {
	ret, _ := bigip.ExecuteCommand("show ltm node " + name + " field-fmt")
	if strings.Contains(ret, "was not found.") {
		return nil, fmt.Errorf(ret)
	}
	node := ParseShowLtmNode(ret)
	return node, nil
}

func (bigip *BigIP) CreateNode(name, ipaddr string) error {
	ret, _ := bigip.ExecuteCommand("create ltm node " + name + " address " + ipaddr)
	if ret != "" {
		return fmt.Errorf(ret)
	}
	return nil
}

func (bigip *BigIP) DeleteNode(name string) error {
	ret, _ := bigip.ExecuteCommand("delete ltm node " + name)
	if ret != "" {
		return fmt.Errorf(ret)
	}
	return nil
}

func (bigip *BigIP) EnableNode(name string) error {
	ret, _ := bigip.ExecuteCommand("modify ltm node " + name + " session user-enabled")
	if ret != "" {
		return fmt.Errorf(ret)
	}
	return nil
}

func (bigip *BigIP) DisableNode(name string) error {
	ret, _ := bigip.ExecuteCommand("modify ltm node " + name + " session user-disabled")
	if ret != "" {
		return fmt.Errorf(ret)
	}
	return nil
}

func (bigip *BigIP) GetPool(name string) (*Pool, error) {
	ret, _ := bigip.ExecuteCommand("show ltm pool " + name + " members field-fmt")
	if strings.Contains(ret, "was not found.") {
		return nil, fmt.Errorf(ret)
	}
	pool := ParseShowLtmPool(ret)
	return pool, nil
}

func (bigip *BigIP) CreatePool(name string) error {
	ret, _ := bigip.ExecuteCommand("create ltm pool " + name)
	if ret != "" {
		return fmt.Errorf(ret)
	}
	return nil
}

func (bigip *BigIP) DeletePool(name string) error {
	ret, _ := bigip.ExecuteCommand("delete ltm pool " + name)
	if ret != "" {
		return fmt.Errorf(ret)
	}
	return nil
}

func (bigip *BigIP) AddMonitorToPool(poolName, monitorName string) error {
	ret, _ := bigip.ExecuteCommand("modify ltm pool " + poolName + " monitor '" + monitorName + "'")
	if ret != "" {
		return fmt.Errorf(ret)
	}
	return nil
}

func (bigip *BigIP) AddPoolMember(poolName, nodeName, monitorName string, port int) error {
	member := nodeName + ":" + strconv.Itoa(port)
	cmd := "modify ltm pool " + poolName + " members add { " + member + " } monitor '" + monitorName + "'"
	ret, _ := bigip.ExecuteCommand(cmd)
	if ret != "" {
		return fmt.Errorf(ret)
	}
	return nil
}

func (bigip *BigIP) DeletePoolMember(poolName, nodeName string, port int) error {
	member := nodeName + ":" + strconv.Itoa(port)
	cmd := "modify ltm pool " + poolName + " members delete { " + member + " }"
	ret, _ := bigip.ExecuteCommand(cmd)
	if ret != "" {
		return fmt.Errorf(ret)
	}
	return nil
}

func (bigip *BigIP) EnablePoolMember(poolName, nodeName string, port int) error {
	member := nodeName + ":" + strconv.Itoa(port)
	cmd := "modify ltm pool " + poolName + " members modify { " + member + " { session user-enabled } }"
	ret, _ := bigip.ExecuteCommand(cmd)
	if ret != "" {
		return fmt.Errorf(ret)
	}
	return nil
}

func (bigip *BigIP) DisablePoolMember(poolName, nodeName string, port int) error {
	member := nodeName + ":" + strconv.Itoa(port)
	cmd := "modify ltm pool " + poolName + " members modify { " + member + " { session user-disabled } }"
	ret, _ := bigip.ExecuteCommand(cmd)
	if ret != "" {
		return fmt.Errorf(ret)
	}
	return nil
}

func (bigip *BigIP) GetVirtualServer(name string) (*VirtualServer, error) {
	ret, _ := bigip.ExecuteCommand("list ltm virtual " + name)
	if strings.Contains(ret, "was not found.") {
		return nil, fmt.Errorf(ret)
	}
	vs := ParseListLtmVirtual(ret)
	return vs, nil

}

func (bigip *BigIP) CreateVirtualServer(vsName, poolName, targetVIP, defaultProfileName string, targetPort int) error {
	destination := targetVIP + ":" + strconv.Itoa(targetPort)
	cmd := "create ltm virtual " + vsName + " { destination " + destination + " ip-protocol tcp mask 255.255.255.255 pool " + poolName + " profiles add { " + defaultProfileName + " } }"
	ret, _ := bigip.ExecuteCommand(cmd)
	if ret != "" {
		return fmt.Errorf(ret)
	}
	return nil
}

func (bigip *BigIP) DeleteVirtualServer(vsName string) error {
	ret, _ := bigip.ExecuteCommand("delete ltm virtual " + vsName)
	if ret != "" {
		return fmt.Errorf(ret)
	}
	return nil
}

func (bigip *BigIP) AddVirtualServerProfile(vsName, profileName, context string) error {
	cmd := "modify ltm virtual " + vsName + " profiles add { " + profileName + " { context " + context + " } }"
	ret, _ := bigip.ExecuteCommand(cmd)
	if ret != "" {
		return fmt.Errorf(ret)
	}
	return nil
}

func (bigip *BigIP) DeleteVirtualServerProfile(vsName, profileName, context string) error {
	cmd := "modify ltm virtual " + vsName + " profiles delete { " + profileName + " }"
	ret, _ := bigip.ExecuteCommand(cmd)
	if ret != "" {
		return fmt.Errorf(ret)
	}
	return nil
}

func (bigip *BigIP) ApplyPolicyToVirtualServer(vsName, policyName string) error {
	cmd := "modify ltm virtual " + vsName + " fw-enforced-policy " + policyName
	ret, _ := bigip.ExecuteCommand(cmd)
	if ret != "" {
		return fmt.Errorf(ret)
	}
	return nil
}
<|endoftext|>


<commit_before>package queue

import (
	"log"

	"github.com/streadway/amqp"
)

type QueueConnection struct {
	Connection *amqp.Connection
	Channel    *amqp.Channel

	HandleChannelClose func(message string)
	HandleFatalError   func(err *amqp.Error)

	notifyClose chan *amqp.Error
}

func NewQueueConnection(amqpURI string) (*QueueConnection, error) {
	connection, err := amqp.Dial(amqpURI)
	if err != nil {
		return nil, err
	}

	channel, err := connection.Channel()
	if err != nil {
		return nil, err
	}

	err = channel.Qos(5, 0, false)
	if err != nil {
		return nil, err
	}

	queueConnection := &QueueConnection{
		Connection:         connection,
		Channel:            channel,
		HandleChannelClose: func(message string) { log.Println(message) },
		HandleFatalError:   func(err *amqp.Error) { log.Fatalln(err) },
		notifyClose:        channel.NotifyClose(make(chan *amqp.Error)),
	}

	go func() {
		select {
		case e, ok := <-queueConnection.notifyClose:
			if e != nil && !e.Recover {
				queueConnection.HandleFatalError(e)
			}

			if !ok {
				queueConnection.HandleChannelClose("Channel closed")
			}
		}
	}()

	return queueConnection, nil
}

func (c *QueueConnection) Close() error {
	err := c.Channel.Close()
	if err != nil {
		return err
	}

	return c.Connection.Close()
}

func (c *QueueConnection) Consume(queueName string) (<-chan amqp.Delivery, error) {
	return c.Channel.Consume(
		queueName,
		"",
		false, // autoAck
		false, // this won't be the sole consumer
		true,  // don't deliver messages from same connection
		false, // the broker owns when consumption can begin
		nil)   // arguments
}

func (c *QueueConnection) ExchangeDeclare(exchangeName string, exchangeType string) error {
	return c.Channel.ExchangeDeclare(
		exchangeName, // name of the exchange
		exchangeType, // type
		true,         // durable
		false,        // delete when complete
		false,        // internal
		false,        // noWait
		nil,          // arguments
	)
}

func (c *QueueConnection) QueueDeclare(queueName string) (amqp.Queue, error) {
	queue, err := c.Channel.QueueDeclare(
		queueName, // name of the queue
		true,      // durable
		false,     // delete when usused
		false,     // exclusive
		false,     // noWait
		nil)       // arguments
	if err != nil {
		return amqp.Queue{
			Name: queueName,
		}, err
	}

	return queue, nil
}

func (c *QueueConnection) BindQueueToExchange(queueName string, exchangeName string) error {
	return c.Channel.QueueBind(
		queueName,
		"#", // key to marshall with
		exchangeName,
		true, // noWait
		nil)  // arguments
}

func (c *QueueConnection) Publish(exchangeName string, routingKey string, contentType string, body string) error {
	return c.Channel.Publish(
		exchangeName, // publish to an exchange
		routingKey,   // routing to 0 or more queues
		false,        // mandatory
		false,        // immediate
		amqp.Publishing{
			Headers:         amqp.Table{},
			ContentType:     contentType,
			ContentEncoding: "",
			Body:            []byte(body),
			DeliveryMode:    amqp.Persistent,
			Priority:        0, // 0-9
		})
}
<commit_msg>Exit if the RabbitMQ channel closes<commit_after>package queue

import (
	"log"

	"github.com/streadway/amqp"
)

type QueueConnection struct {
	Connection *amqp.Connection
	Channel    *amqp.Channel

	HandleChannelClose func(message string)
	HandleFatalError   func(err *amqp.Error)

	notifyClose chan *amqp.Error
}

func NewQueueConnection(amqpURI string) (*QueueConnection, error) {
	connection, err := amqp.Dial(amqpURI)
	if err != nil {
		return nil, err
	}

	channel, err := connection.Channel()
	if err != nil {
		return nil, err
	}

	err = channel.Qos(5, 0, false)
	if err != nil {
		return nil, err
	}

	queueConnection := &QueueConnection{
		Connection:         connection,
		Channel:            channel,
		HandleChannelClose: func(message string) { log.Fatalln(message) },
		HandleFatalError:   func(err *amqp.Error) { log.Fatalln(err) },
		notifyClose:        channel.NotifyClose(make(chan *amqp.Error)),
	}

	go func() {
		select {
		case e, ok := <-queueConnection.notifyClose:
			if e != nil && !e.Recover {
				queueConnection.HandleFatalError(e)
			}

			if !ok {
				queueConnection.HandleChannelClose("Channel closed")
			}
		}
	}()

	return queueConnection, nil
}

func (c *QueueConnection) Close() error {
	err := c.Channel.Close()
	if err != nil {
		return err
	}

	return c.Connection.Close()
}

func (c *QueueConnection) Consume(queueName string) (<-chan amqp.Delivery, error) {
	return c.Channel.Consume(
		queueName,
		"",
		false, // autoAck
		false, // this won't be the sole consumer
		true,  // don't deliver messages from same connection
		false, // the broker owns when consumption can begin
		nil)   // arguments
}

func (c *QueueConnection) ExchangeDeclare(exchangeName string, exchangeType string) error {
	return c.Channel.ExchangeDeclare(
		exchangeName, // name of the exchange
		exchangeType, // type
		true,         // durable
		false,        // delete when complete
		false,        // internal
		false,        // noWait
		nil,          // arguments
	)
}

func (c *QueueConnection) QueueDeclare(queueName string) (amqp.Queue, error) {
	queue, err := c.Channel.QueueDeclare(
		queueName, // name of the queue
		true,      // durable
		false,     // delete when usused
		false,     // exclusive
		false,     // noWait
		nil)       // arguments
	if err != nil {
		return amqp.Queue{
			Name: queueName,
		}, err
	}

	return queue, nil
}

func (c *QueueConnection) BindQueueToExchange(queueName string, exchangeName string) error {
	return c.Channel.QueueBind(
		queueName,
		"#", // key to marshall with
		exchangeName,
		true, // noWait
		nil)  // arguments
}

func (c *QueueConnection) Publish(exchangeName string, routingKey string, contentType string, body string) error {
	return c.Channel.Publish(
		exchangeName, // publish to an exchange
		routingKey,   // routing to 0 or more queues
		false,        // mandatory
		false,        // immediate
		amqp.Publishing{
			Headers:         amqp.Table{},
			ContentType:     contentType,
			ContentEncoding: "",
			Body:            []byte(body),
			DeliveryMode:    amqp.Persistent,
			Priority:        0, // 0-9
		})
}
<|endoftext|>


<commit_before>package rabbitsmpp

import (
	"fmt"
	"os/exec"
	"strconv"
	"strings"
	"sync"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/stretchr/testify/suite"
)

const (
	queueName = "testQueue"
	rabbitURL = "amqp://localhost:33192"
)

func StartRabbitMQContainerWithPort(port int) error {
	portStr := strconv.Itoa(port)
	cmdStatement := fmt.Sprintf("docker run --name rabbit-delay-test -p :%s:5672 -d rabbitmq", portStr)
	cmdParts := strings.Split(cmdStatement, " ")
	cmd := exec.Command(cmdParts[0], cmdParts[1:]...)
	return cmd.Run()
}

func RemoveRabbitMQContainer() error {
	cmdStatement := fmt.Sprintf("docker rm -f rabbit-delay-test")
	cmdParts := strings.Split(cmdStatement, " ")
	cmd := exec.Command(cmdParts[0], cmdParts[1:]...)
	return cmd.Run()
}

type ClientSuite struct {
	config Config
	suite.Suite
}

func TestClientSuite(t *testing.T) {
	RemoveRabbitMQContainer()
	StartRabbitMQContainerWithPort(33192)
	time.Sleep(5 * time.Second)

	s := &ClientSuite{}
	s.config = Config{
		URL:       rabbitURL,
		QueueName: queueName,
	}
	suite.Run(t, s)
}

func (s *ClientSuite) TearDownSuite() {
	RemoveRabbitMQContainer()
}

func (s *ClientSuite) TearDownTest() {
	client, _ := NewClient(s.config)
	require.NotNil(s.T(), client)
	ch, _ := client.Channel()
	require.NotNil(s.T(), ch)
	ch.QueuePurge(queueName, false)
}

func (s *ClientSuite) CheckQueue(ch Channel, numMessages int) {
	queue, err := ch.QueueInspect(queueName)
	require.NoError(s.T(), err)
	assert.Equal(s.T(), numMessages, queue.Messages)
}

func (s *ClientSuite) ConsumeQueue(jobChan <-chan Job, errChan <-chan error, numMessages int) {
	jobCounter := 0
	errCounter := 0
	wg := sync.WaitGroup{}
	wg.Add(1)
	go func() {
		defer wg.Done()
		for {
			select {
			case j, ok := <-jobChan:
				if !ok {
					return
				}
				jobCounter++
				j.Ack(false)
			case _, ok := <-errChan:
				if !ok {
					return
				}
				errCounter++
			case <-time.After(1 * time.Second):
				return
			}
		}
	}()
	wg.Wait()
	assert.Equal(s.T(), numMessages, jobCounter)
	assert.Equal(s.T(), 0, errCounter)
}

func (s *ClientSuite) testConnReset(resetFunc func()) {
	client, err := NewClient(s.config)
	require.NoError(s.T(), err)
	ch, err := client.Channel()
	require.NoError(s.T(), err)
	require.NotNil(s.T(), ch)
	closeChan := client.GetCloseChan()

	publisher, err := NewPublisher(s.config)
	require.NoError(s.T(), err)

	consumer, err := NewConsumer(s.config)
	require.NoError(s.T(), err)
	jobChan, errChan, err := consumer.Consume()
	require.NoError(s.T(), err)

	numMessages := 50
	for i := 0; i < numMessages; i++ {
		err = publisher.Publish(Job{})
		require.NoError(s.T(), err)
	}
	time.Sleep(1 * time.Second)
	// this is because right when consumer starts, it caches 'defaultPrefetchCount' messages
	s.CheckQueue(ch, numMessages-defaultPrefetchCount)
	s.ConsumeQueue(jobChan, errChan, numMessages)
	s.CheckQueue(ch, 0)

	resetFunc()

	ch, err = client.Channel()
	require.NoError(s.T(), err)
	require.NotNil(s.T(), ch)
	select {
	case <-closeChan:
	default:
		require.Fail(s.T(), "closeChan did not receive notification")
	}

	for i := 0; i < numMessages; i++ {
		err = publisher.Publish(Job{})
		require.NoError(s.T(), err)
	}
	time.Sleep(1 * time.Second)
	// this is because right when consumer starts, it caches 'defaultPrefetchCount' messages
	s.CheckQueue(ch, numMessages-defaultPrefetchCount)
	s.ConsumeQueue(jobChan, errChan, numMessages)
	s.CheckQueue(ch, 0)
	err = consumer.Stop()
	require.NoError(s.T(), err)
}

func (s *ClientSuite) TestConnClose() {
	resetFunc := func() {
		CloseConn()
		time.Sleep(3 * time.Second)
	}
	s.testConnReset(resetFunc)
}

func (s *ClientSuite) TestRQTeardown() {

	resetFunc := func() {
		RemoveRabbitMQContainer()
		time.Sleep(1 * time.Second)

		badClient, err := NewClient(s.config)
		require.NoError(s.T(), err)
		require.NotNil(s.T(), badClient)
		ch, err := badClient.Channel()
		require.Error(s.T(), err)
		require.Nil(s.T(), ch)

		StartRabbitMQContainerWithPort(33192)
		time.Sleep(10 * time.Second)

	}

	s.testConnReset(resetFunc)
}

func (s *ClientSuite) TestAConnDownThenUp() {
	RemoveRabbitMQContainer()
	time.Sleep(1 * time.Second)

	client, err := NewClient(s.config)
	require.Error(s.T(), err)

	StartRabbitMQContainerWithPort(33192)
	time.Sleep(5 * time.Second)

	client, err = NewClient(s.config)
	require.NoError(s.T(), err)
	ch, err := client.Channel()
	require.NoError(s.T(), err)
	require.NotNil(s.T(), ch)
}
<commit_msg>Increace wait time<commit_after>package rabbitsmpp

import (
	"fmt"
	"os/exec"
	"strconv"
	"strings"
	"sync"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/stretchr/testify/suite"
)

const (
	queueName = "testQueue"
	rabbitURL = "amqp://localhost:33192"
)

func StartRabbitMQContainerWithPort(port int) error {
	portStr := strconv.Itoa(port)
	cmdStatement := fmt.Sprintf("docker run --name rabbit-delay-test -p :%s:5672 -d rabbitmq", portStr)
	cmdParts := strings.Split(cmdStatement, " ")
	cmd := exec.Command(cmdParts[0], cmdParts[1:]...)
	return cmd.Run()
}

func RemoveRabbitMQContainer() error {
	cmdStatement := fmt.Sprintf("docker rm -f rabbit-delay-test")
	cmdParts := strings.Split(cmdStatement, " ")
	cmd := exec.Command(cmdParts[0], cmdParts[1:]...)
	return cmd.Run()
}

type ClientSuite struct {
	config Config
	suite.Suite
}

func TestClientSuite(t *testing.T) {
	RemoveRabbitMQContainer()
	StartRabbitMQContainerWithPort(33192)
	time.Sleep(5 * time.Second)

	s := &ClientSuite{}
	s.config = Config{
		URL:       rabbitURL,
		QueueName: queueName,
	}
	suite.Run(t, s)
}

func (s *ClientSuite) TearDownSuite() {
	RemoveRabbitMQContainer()
}

func (s *ClientSuite) TearDownTest() {
	client, _ := NewClient(s.config)
	require.NotNil(s.T(), client)
	ch, _ := client.Channel()
	require.NotNil(s.T(), ch)
	ch.QueuePurge(queueName, false)
}

func (s *ClientSuite) CheckQueue(ch Channel, numMessages int) {
	queue, err := ch.QueueInspect(queueName)
	require.NoError(s.T(), err)
	assert.Equal(s.T(), numMessages, queue.Messages)
}

func (s *ClientSuite) ConsumeQueue(jobChan <-chan Job, errChan <-chan error, numMessages int) {
	jobCounter := 0
	errCounter := 0
	wg := sync.WaitGroup{}
	wg.Add(1)
	go func() {
		defer wg.Done()
		for {
			select {
			case j, ok := <-jobChan:
				if !ok {
					return
				}
				jobCounter++
				j.Ack(false)
			case _, ok := <-errChan:
				if !ok {
					return
				}
				errCounter++
			case <-time.After(1 * time.Second):
				return
			}
		}
	}()
	wg.Wait()
	assert.Equal(s.T(), numMessages, jobCounter)
	assert.Equal(s.T(), 0, errCounter)
}

func (s *ClientSuite) testConnReset(resetFunc func()) {
	client, err := NewClient(s.config)
	require.NoError(s.T(), err)
	ch, err := client.Channel()
	require.NoError(s.T(), err)
	require.NotNil(s.T(), ch)
	closeChan := client.GetCloseChan()

	publisher, err := NewPublisher(s.config)
	require.NoError(s.T(), err)

	consumer, err := NewConsumer(s.config)
	require.NoError(s.T(), err)
	jobChan, errChan, err := consumer.Consume()
	require.NoError(s.T(), err)

	numMessages := 50
	for i := 0; i < numMessages; i++ {
		err = publisher.Publish(Job{})
		require.NoError(s.T(), err)
	}
	time.Sleep(1 * time.Second)
	// this is because right when consumer starts, it caches 'defaultPrefetchCount' messages
	s.CheckQueue(ch, numMessages-defaultPrefetchCount)
	s.ConsumeQueue(jobChan, errChan, numMessages)
	s.CheckQueue(ch, 0)

	resetFunc()

	ch, err = client.Channel()
	require.NoError(s.T(), err)
	require.NotNil(s.T(), ch)
	select {
	case <-closeChan:
	default:
		require.Fail(s.T(), "closeChan did not receive notification")
	}

	for i := 0; i < numMessages; i++ {
		err = publisher.Publish(Job{})
		require.NoError(s.T(), err)
	}
	time.Sleep(1 * time.Second)
	// this is because right when consumer starts, it caches 'defaultPrefetchCount' messages
	s.CheckQueue(ch, numMessages-defaultPrefetchCount)
	s.ConsumeQueue(jobChan, errChan, numMessages)
	s.CheckQueue(ch, 0)
	err = consumer.Stop()
	require.NoError(s.T(), err)
}

func (s *ClientSuite) TestConnClose() {
	resetFunc := func() {
		CloseConn()
		time.Sleep(3 * time.Second)
	}
	s.testConnReset(resetFunc)
}

func (s *ClientSuite) TestRQTeardown() {

	resetFunc := func() {
		RemoveRabbitMQContainer()
		time.Sleep(1 * time.Second)

		badClient, err := NewClient(s.config)
		require.NoError(s.T(), err)
		require.NotNil(s.T(), badClient)
		ch, err := badClient.Channel()
		require.Error(s.T(), err)
		require.Nil(s.T(), ch)

		StartRabbitMQContainerWithPort(33192)
		time.Sleep(10 * time.Second)

	}

	s.testConnReset(resetFunc)
}

func (s *ClientSuite) TestAConnDownThenUp() {
	RemoveRabbitMQContainer()
	time.Sleep(1 * time.Second)

	client, err := NewClient(s.config)
	require.Error(s.T(), err)

	StartRabbitMQContainerWithPort(33192)
	time.Sleep(10 * time.Second)

	client, err = NewClient(s.config)
	require.NoError(s.T(), err)
	ch, err := client.Channel()
	require.NoError(s.T(), err)
	require.NotNil(s.T(), ch)
}
<|endoftext|>


<commit_before>package rainforest

import (
	"errors"
	"strconv"
	"time"
)

// Generator is a type representing generators which can be used as variables in RF tests.
// They can be builtin or uploaded by customer as a tabular variable.
type Generator struct {
	ID           int               `json:"id,omitempty"`
	Name         string            `json:"name,omitempty"`
	CreatedAt time.Time         `json:"created_at,omitempty"`
	Description  string            `json:"description,omitempty"`
	Type         string            `json:"generator_type,omitempty"`
	SingleUse    bool              `json:"single_use,omitempty"`
	Columns      []GeneratorColumn `json:"columns,omitempty"`
	RowCount     int               `json:"row_count,omitempty"`
}

// GetID returns the Generator name
func (g Generator) GetID() string {
	return g.Name
}

// GetDescription returns the Generator's description
func (g Generator) GetDescription() string {
	return g.Description
}

// GeneratorColumn is a type of column in a generator
type GeneratorColumn struct {
	ID           int       `json:"id,omitempty"`
	CreatedAt time.Time `json:"created_at,omitempty"`
	Name         string    `json:"name,omitempty"`
}

// GeneratorRelatedTests is a type which holds tests where the generator has been used
type GeneratorRelatedTests struct {
	ID    int    `json:"id,omitempty"`
	Title string `json:"title,omitempty"`
}

// GetGenerators fetches a list of all available generators for the account
func (c *Client) GetGenerators() ([]Generator, error) {
	// Prepare request
	req, err := c.NewRequest("GET", "generators", nil)
	if err != nil {
		return nil, err
	}

	// Send request and process response
	var generatorResp []Generator
	_, err = c.Do(req, &generatorResp)
	if err != nil {
		return nil, err
	}
	return generatorResp, nil
}

// DeleteGenerator deletes generator with specified ID
func (c *Client) DeleteGenerator(genID int) error {
	// Prepare request
	req, err := c.NewRequest("DELETE", "generators/"+strconv.Itoa(genID), nil)
	if err != nil {
		return err
	}

	// Fire away the request
	_, err = c.Do(req, nil)
	if err != nil {
		return err
	}

	// And just nil when we're done
	return nil
}

// CreateTabularVar creates new tabular variable on RF and returns Generator associated with it
// columns argument should contain just an array of column names, contents of the generator should
// be filled using AddGeneratorRows.
func (c *Client) CreateTabularVar(name, description string,
	columns []string, singleUse bool) (*Generator, error) {
	//Prepare request
	type genCreateRequest struct {
		Name        string   `json:"name,omitempty"`
		Description string   `json:"description,omitempty"`
		SingleUse   bool     `json:"single_use,omitempty"`
		Columns     []string `json:"columns,omitempty"`
	}
	body := genCreateRequest{name, description, singleUse, columns}
	req, err := c.NewRequest("POST", "generators", body)
	if err != nil {
		return &Generator{}, err
	}

	var out *Generator
	_, err = c.Do(req, &out)
	if err != nil {
		return out, err
	}

	return out, nil
}

// AddGeneratorRows adds rows to the specified tabular variable
// rowData is in a form of [{ 123: "foo", 124: "bar" }, { 123: "baz", 124: "qux" }] where 123 is a column ID
// AddGeneratorRowsFromTable is also provided which accepts different rowData format
func (c *Client) AddGeneratorRows(targetGenerator *Generator, rowData []map[int]string) error {
	//Prepare request
	type batchRowsRequest struct {
		RowData []map[int]string `json:"data,omitempty"`
	}
	body := batchRowsRequest{rowData}
	reqURL := "generators/" + strconv.Itoa(targetGenerator.ID) + "/rows/batch"
	req, err := c.NewRequest("POST", reqURL, body)
	if err != nil {
		return err
	}

	// Send out the batch
	_, err = c.Do(req, nil)
	if err != nil {
		return err
	}

	return nil
}

// AddGeneratorRowsFromTable adds rows to the specified tabular variable
// data should be formatted as follows:
// targetColumns contains names of existing columns to which add data e.g. ["login", "password"]
// rowData contains row data in columns order specified in targetColumns e.g. [["foo", "bar"], ["baz", "qux"]]
func (c *Client) AddGeneratorRowsFromTable(targetGenerator *Generator,
	targetColumns []string, rowData [][]string) error {
	// Quick sanity check of the args
	if len(targetColumns) != len(targetGenerator.Columns) {
		return errors.New("Invalid number of columns for given generator.")
	}

	// Create a mapping between column names and their id for the Generator
	colNameToID := make(map[string]int)
	for _, column := range targetGenerator.Columns {
		colNameToID[column.Name] = column.ID
	}

	// Use created mapping to create an array of column IDs
	targetColumnIDs := make([]int, len(targetColumns))
	for i, colName := range targetColumns {
		id, ok := colNameToID[colName]
		if !ok {
			return errors.New("Invalid column name for given generator.")
		}
		targetColumnIDs[i] = id
	}

	// Use above helper array to create properly formatted row_data array
	formattedRowData := make([]map[int]string, len(rowData))
	for rowIndex, row := range rowData {
		formattedRow := make(map[int]string)
		if len(row) != len(targetColumns) {
			return errors.New("Invalid number of columns in a row" + strconv.Itoa(rowIndex) +
				"for given generator.")
		}
		for colIndex, colValue := range row {
			formattedRow[targetColumnIDs[colIndex]] = colValue
		}
		formattedRowData[rowIndex] = formattedRow
	}

	// Call the function that will add the rows.
	return c.AddGeneratorRows(targetGenerator, formattedRowData)
}
<commit_msg>formatting<commit_after>package rainforest

import (
	"errors"
	"strconv"
	"time"
)

// Generator is a type representing generators which can be used as variables in RF tests.
// They can be builtin or uploaded by customer as a tabular variable.
type Generator struct {
	ID          int               `json:"id,omitempty"`
	Name        string            `json:"name,omitempty"`
	CreatedAt   time.Time         `json:"created_at,omitempty"`
	Description string            `json:"description,omitempty"`
	Type        string            `json:"generator_type,omitempty"`
	SingleUse   bool              `json:"single_use,omitempty"`
	Columns     []GeneratorColumn `json:"columns,omitempty"`
	RowCount    int               `json:"row_count,omitempty"`
}

// GetID returns the Generator name
func (g Generator) GetID() string {
	return g.Name
}

// GetDescription returns the Generator's description
func (g Generator) GetDescription() string {
	return g.Description
}

// GeneratorColumn is a type of column in a generator
type GeneratorColumn struct {
	ID        int       `json:"id,omitempty"`
	CreatedAt time.Time `json:"created_at,omitempty"`
	Name      string    `json:"name,omitempty"`
}

// GeneratorRelatedTests is a type which holds tests where the generator has been used
type GeneratorRelatedTests struct {
	ID    int    `json:"id,omitempty"`
	Title string `json:"title,omitempty"`
}

// GetGenerators fetches a list of all available generators for the account
func (c *Client) GetGenerators() ([]Generator, error) {
	// Prepare request
	req, err := c.NewRequest("GET", "generators", nil)
	if err != nil {
		return nil, err
	}

	// Send request and process response
	var generatorResp []Generator
	_, err = c.Do(req, &generatorResp)
	if err != nil {
		return nil, err
	}
	return generatorResp, nil
}

// DeleteGenerator deletes generator with specified ID
func (c *Client) DeleteGenerator(genID int) error {
	// Prepare request
	req, err := c.NewRequest("DELETE", "generators/"+strconv.Itoa(genID), nil)
	if err != nil {
		return err
	}

	// Fire away the request
	_, err = c.Do(req, nil)
	if err != nil {
		return err
	}

	// And just nil when we're done
	return nil
}

// CreateTabularVar creates new tabular variable on RF and returns Generator associated with it
// columns argument should contain just an array of column names, contents of the generator should
// be filled using AddGeneratorRows.
func (c *Client) CreateTabularVar(name, description string,
	columns []string, singleUse bool) (*Generator, error) {
	//Prepare request
	type genCreateRequest struct {
		Name        string   `json:"name,omitempty"`
		Description string   `json:"description,omitempty"`
		SingleUse   bool     `json:"single_use,omitempty"`
		Columns     []string `json:"columns,omitempty"`
	}
	body := genCreateRequest{name, description, singleUse, columns}
	req, err := c.NewRequest("POST", "generators", body)
	if err != nil {
		return &Generator{}, err
	}

	var out *Generator
	_, err = c.Do(req, &out)
	if err != nil {
		return out, err
	}

	return out, nil
}

// AddGeneratorRows adds rows to the specified tabular variable
// rowData is in a form of [{ 123: "foo", 124: "bar" }, { 123: "baz", 124: "qux" }] where 123 is a column ID
// AddGeneratorRowsFromTable is also provided which accepts different rowData format
func (c *Client) AddGeneratorRows(targetGenerator *Generator, rowData []map[int]string) error {
	//Prepare request
	type batchRowsRequest struct {
		RowData []map[int]string `json:"data,omitempty"`
	}
	body := batchRowsRequest{rowData}
	reqURL := "generators/" + strconv.Itoa(targetGenerator.ID) + "/rows/batch"
	req, err := c.NewRequest("POST", reqURL, body)
	if err != nil {
		return err
	}

	// Send out the batch
	_, err = c.Do(req, nil)
	if err != nil {
		return err
	}

	return nil
}

// AddGeneratorRowsFromTable adds rows to the specified tabular variable
// data should be formatted as follows:
// targetColumns contains names of existing columns to which add data e.g. ["login", "password"]
// rowData contains row data in columns order specified in targetColumns e.g. [["foo", "bar"], ["baz", "qux"]]
func (c *Client) AddGeneratorRowsFromTable(targetGenerator *Generator,
	targetColumns []string, rowData [][]string) error {
	// Quick sanity check of the args
	if len(targetColumns) != len(targetGenerator.Columns) {
		return errors.New("Invalid number of columns for given generator.")
	}

	// Create a mapping between column names and their id for the Generator
	colNameToID := make(map[string]int)
	for _, column := range targetGenerator.Columns {
		colNameToID[column.Name] = column.ID
	}

	// Use created mapping to create an array of column IDs
	targetColumnIDs := make([]int, len(targetColumns))
	for i, colName := range targetColumns {
		id, ok := colNameToID[colName]
		if !ok {
			return errors.New("Invalid column name for given generator.")
		}
		targetColumnIDs[i] = id
	}

	// Use above helper array to create properly formatted row_data array
	formattedRowData := make([]map[int]string, len(rowData))
	for rowIndex, row := range rowData {
		formattedRow := make(map[int]string)
		if len(row) != len(targetColumns) {
			return errors.New("Invalid number of columns in a row" + strconv.Itoa(rowIndex) +
				"for given generator.")
		}
		for colIndex, colValue := range row {
			formattedRow[targetColumnIDs[colIndex]] = colValue
		}
		formattedRowData[rowIndex] = formattedRow
	}

	// Call the function that will add the rows.
	return c.AddGeneratorRows(targetGenerator, formattedRowData)
}
<|endoftext|>


<commit_before>package show

import (
	"fmt"
	"image"
	"image/color"
	"strconv"

	oak "github.com/oakmound/oak/v2"
	"github.com/oakmound/oak/v2/event"
	"github.com/oakmound/oak/v2/render"
	"github.com/oakmound/oak/v2/scene"
)

type Slide interface {
	Init()
	Continue() bool
	Prev() bool
	Transition() scene.Transition
}

func slideResult(sl Slide) *scene.Result {
	return &scene.Result{
		Transition: sl.Transition(),
	}
}

var (
	skip   bool
	skipTo string
)

func AddNumberShortcuts(max int) {
	oak.AddCommand("slide", func(args []string) {
		if len(args) < 2 {
			return
		}
		v := args[1]
		i, err := strconv.Atoi(v)
		if err != nil {
			fmt.Println(err)
			return
		}
		if i < 0 {
			skipTo = "0"
		} else if i <= max {
			skipTo = v
		} else {
			skipTo = strconv.Itoa(max)
		}
		skip = true
	})
}

func Start(width, height int, slides ...Slide) {
	for i, sl := range slides {
		i := i
		sl := sl
		oak.AddScene("slide"+strconv.Itoa(i), scene.Scene{
			Start: func(*scene.Context) { sl.Init() },
			Loop: func() bool {
				cont := sl.Continue() && !skip
				// This should be disable-able
				if !cont {
					oak.SetLoadingRenderable(render.NewSprite(0, 0, oak.ScreenShot()))
				}
				return cont
			},
			End: func() (string, *scene.Result) {

				if skip {
					skip = false
					return "slide" + skipTo, slideResult(sl)
				}
				if sl.Prev() {
					if i > 0 {
						return "slide" + strconv.Itoa(i-1), slideResult(sl)
					}
					return "slide0", slideResult(sl)
				}
				return "slide" + strconv.Itoa(i+1), slideResult(sl)
			},
		})
	}

	reset := false

	var oldBackground image.Image

	// Todo: customizable end slide
	oak.AddScene("slide"+strconv.Itoa(len(slides)),
		scene.Scene{
			Start: func(ctx *scene.Context) {
				oldBackground = oak.GetBackgroundImage()
				oak.SetColorBackground(image.NewUniform(color.RGBA{0, 0, 0, 255}))
				render.Draw(
					Express.NewStrText(
						"Spacebar to restart show ...",
						float64(ctx.Window.Width()/2),
						float64(ctx.Window.Height()-50),
					),
				)
				event.GlobalBind("KeyDownSpacebar", func(event.CID, interface{}) int {
					reset = true
					return 0
				})
			},
			Loop: func() bool {
				return !reset
			},
			End: func() (string, *scene.Result) {
				oak.SetColorBackground(oldBackground)
				reset = false
				skip = false
				return "slide0", nil
			},
		},
	)
	oak.Init("slide0", func(c oak.Config) (oak.Config, error) {
		c.Screen = oak.Screen{
			Width:  width,
			Height: height,
		}
		c.FrameRate = 30
		c.DrawFrameRate = 30
		return c, nil
	})
}
<commit_msg>examples/slide/show fix screen settings for slides to not break for lack of scale<commit_after>package show

import (
	"fmt"
	"image"
	"image/color"
	"strconv"

	oak "github.com/oakmound/oak/v2"
	"github.com/oakmound/oak/v2/event"
	"github.com/oakmound/oak/v2/render"
	"github.com/oakmound/oak/v2/scene"
)

type Slide interface {
	Init()
	Continue() bool
	Prev() bool
	Transition() scene.Transition
}

func slideResult(sl Slide) *scene.Result {
	return &scene.Result{
		Transition: sl.Transition(),
	}
}

var (
	skip   bool
	skipTo string
)

func AddNumberShortcuts(max int) {
	oak.AddCommand("slide", func(args []string) {
		if len(args) < 2 {
			return
		}
		v := args[1]
		i, err := strconv.Atoi(v)
		if err != nil {
			fmt.Println(err)
			return
		}
		if i < 0 {
			skipTo = "0"
		} else if i <= max {
			skipTo = v
		} else {
			skipTo = strconv.Itoa(max)
		}
		skip = true
	})
}

func Start(width, height int, slides ...Slide) {
	for i, sl := range slides {
		i := i
		sl := sl
		oak.AddScene("slide"+strconv.Itoa(i), scene.Scene{
			Start: func(*scene.Context) { sl.Init() },
			Loop: func() bool {
				cont := sl.Continue() && !skip
				// This should be disable-able
				if !cont {
					oak.SetLoadingRenderable(render.NewSprite(0, 0, oak.ScreenShot()))
				}
				return cont
			},
			End: func() (string, *scene.Result) {

				if skip {
					skip = false
					return "slide" + skipTo, slideResult(sl)
				}
				if sl.Prev() {
					if i > 0 {
						return "slide" + strconv.Itoa(i-1), slideResult(sl)
					}
					return "slide0", slideResult(sl)
				}
				return "slide" + strconv.Itoa(i+1), slideResult(sl)
			},
		})
	}

	reset := false

	var oldBackground image.Image

	// Todo: customizable end slide
	oak.AddScene("slide"+strconv.Itoa(len(slides)),
		scene.Scene{
			Start: func(ctx *scene.Context) {
				oldBackground = oak.GetBackgroundImage()
				oak.SetColorBackground(image.NewUniform(color.RGBA{0, 0, 0, 255}))
				render.Draw(
					Express.NewStrText(
						"Spacebar to restart show ...",
						float64(ctx.Window.Width()/2),
						float64(ctx.Window.Height()-50),
					),
				)
				event.GlobalBind("KeyDownSpacebar", func(event.CID, interface{}) int {
					reset = true
					return 0
				})
			},
			Loop: func() bool {
				return !reset
			},
			End: func() (string, *scene.Result) {
				oak.SetColorBackground(oldBackground)
				reset = false
				skip = false
				return "slide0", nil
			},
		},
	)
	oak.Init("slide0", func(c oak.Config) (oak.Config, error) {
		c.Screen.Width = width
		c.Screen.Height = height
		c.FrameRate = 30
		c.DrawFrameRate = 30
		return c, nil
	})
}
<|endoftext|>