<commit_before>package log

import (
	"errors"
	log "github.com/Sirupsen/logrus"
	"os"
	"runtime"

	"github.com/goadesign/goa/middleware"
	"golang.org/x/net/context"
)

var (
	logger = log.New()
)

func InitializeLogger(developerModeFlag bool) {
	logger = log.New()

	if developerModeFlag {
		customFormatter := new(log.TextFormatter)
		customFormatter.FullTimestamp = true
		customFormatter.TimestampFormat = "2006-01-02 15:04:05"
		log.SetFormatter(customFormatter)

		log.SetLevel(log.DebugLevel)
		logger.Level = log.DebugLevel
		logger.Formatter = customFormatter
	} else {
		customFormatter := new(log.JSONFormatter)
		customFormatter.TimestampFormat = "2006-01-02 15:04:05"

		log.SetFormatter(customFormatter)
		customFormatter.DisableTimestamp = false

		log.SetLevel(log.InfoLevel)
		logger.Level = log.InfoLevel
		logger.Formatter = customFormatter
	}

	logger.Out = os.Stdout

}

func NewCustomizedLogger(level string, developerModeFlag bool) (*log.Logger, error) {
	logger := log.New()

	lv, err := log.ParseLevel(level)
	if err != nil {
		return nil, err
	}
	logger.Level = lv

	if developerModeFlag {
		customFormatter := new(log.TextFormatter)
		customFormatter.FullTimestamp = true
		customFormatter.TimestampFormat = "2006-01-02 15:04:05"
		log.SetFormatter(customFormatter)

		log.SetLevel(log.DebugLevel)
		logger.Level = lv
		logger.Formatter = customFormatter
	} else {
		customFormatter := new(log.JSONFormatter)
		customFormatter.TimestampFormat = "2006-01-02 15:04:05"

		log.SetFormatter(customFormatter)
		customFormatter.DisableTimestamp = false

		log.SetLevel(log.InfoLevel)
		logger.Level = lv
		logger.Formatter = customFormatter
	}

	logger.Out = os.Stdout

	return logger, nil
}

func Logger() *log.Logger {
	return logger
}

func Error(ctx context.Context, fields map[string]interface{}, format string, args ...interface{}) {
	if logger.Level >= log.ErrorLevel {
		entry := log.WithField("pid", os.Getpid())

		file, line, fName, err := extractCallerDetails()
		if err != nil {
			entry = entry.WithField("file", file).WithField("line", line).WithField("func", fName)
		}

		if ctx != nil {
			reqID := middleware.ContextRequestID(ctx)
			if reqID != "" {
				entry = entry.WithField("requestID", reqID)
			}
		}

		if len(args) > 0 {
			entry.WithFields(fields).Errorf(format, args)
		} else {
			entry.WithFields(fields).Errorln(format)
		}
	}
}

func Warn(ctx context.Context, fields map[string]interface{}, format string, args ...interface{}) {
	if logger.Level >= log.WarnLevel {
		entry := log.NewEntry(logger)

		file, _, fName, err := extractCallerDetails()
		if err != nil {
			entry = log.WithField("file", file).WithField("func", fName)
		}

		if ctx != nil {
			reqID := middleware.ContextRequestID(ctx)
			if reqID != "" {
				entry = entry.WithField("requestID", reqID)
			}
		}
		if len(args) > 0 {
			entry.WithFields(fields).Warnf(format, args...)
		} else {
			entry.WithFields(fields).Warnln(format)
		}
	}
}

func Info(ctx context.Context, fields map[string]interface{}, format string, args ...interface{}) {
	if logger.Level >= log.InfoLevel {
		entry := log.NewEntry(logger)

		if ctx != nil {
			reqID := middleware.ContextRequestID(ctx)
			if reqID != "" {
				entry = entry.WithField("requestID", reqID)
			}
		}

		if len(args) > 0 {
			entry.WithFields(fields).Infof(format, args...)
		} else {
			entry.WithFields(fields).Infoln(format)
		}
	}
}

func Panic(ctx context.Context, fields map[string]interface{}, format string, args ...interface{}) {
	if logger.Level >= log.ErrorLevel {
		entry := log.WithField("pid", os.Getpid())

		if ctx != nil {
			reqID := middleware.ContextRequestID(ctx)
			if reqID != "" {
				entry = entry.WithField("requestID", reqID)
			}
		}

		if len(args) > 0 {
			entry.WithFields(fields).Panicf(format, args)
		} else {
			entry.WithFields(fields).Panicln(format)
		}
	}
}

func Debug(ctx context.Context, fields map[string]interface{}, format string, args ...interface{}) {
	if logger.Level >= log.DebugLevel {
		entry := log.NewEntry(logger)

		if ctx != nil {
			reqID := middleware.ContextRequestID(ctx)
			if reqID != "" {
				entry = entry.WithField("requestID", reqID)
			}
		}

		if len(args) > 0 {
			entry.WithFields(fields).Debugf(format, args...)
		} else {
			entry.WithFields(fields).Debugln(format)
		}
	}
}

func extractCallerDetails() (string, int, string, error) {
	if pc, file, line, ok := runtime.Caller(2); ok {
		fName := runtime.FuncForPC(pc).Name()
		return file, line, fName, nil
	}

	return "", 0, "", errors.New("Unable to extract the caller details")
}
<commit_msg>Rename the requestID log attr to req_id (#799)<commit_after>package log

import (
	"errors"
	log "github.com/Sirupsen/logrus"
	"os"
	"runtime"

	"github.com/goadesign/goa/middleware"
	"golang.org/x/net/context"
)

var (
	logger = log.New()
)

func InitializeLogger(developerModeFlag bool) {
	logger = log.New()

	if developerModeFlag {
		customFormatter := new(log.TextFormatter)
		customFormatter.FullTimestamp = true
		customFormatter.TimestampFormat = "2006-01-02 15:04:05"
		log.SetFormatter(customFormatter)

		log.SetLevel(log.DebugLevel)
		logger.Level = log.DebugLevel
		logger.Formatter = customFormatter
	} else {
		customFormatter := new(log.JSONFormatter)
		customFormatter.TimestampFormat = "2006-01-02 15:04:05"

		log.SetFormatter(customFormatter)
		customFormatter.DisableTimestamp = false

		log.SetLevel(log.InfoLevel)
		logger.Level = log.InfoLevel
		logger.Formatter = customFormatter
	}

	logger.Out = os.Stdout

}

func NewCustomizedLogger(level string, developerModeFlag bool) (*log.Logger, error) {
	logger := log.New()

	lv, err := log.ParseLevel(level)
	if err != nil {
		return nil, err
	}
	logger.Level = lv

	if developerModeFlag {
		customFormatter := new(log.TextFormatter)
		customFormatter.FullTimestamp = true
		customFormatter.TimestampFormat = "2006-01-02 15:04:05"
		log.SetFormatter(customFormatter)

		log.SetLevel(log.DebugLevel)
		logger.Level = lv
		logger.Formatter = customFormatter
	} else {
		customFormatter := new(log.JSONFormatter)
		customFormatter.TimestampFormat = "2006-01-02 15:04:05"

		log.SetFormatter(customFormatter)
		customFormatter.DisableTimestamp = false

		log.SetLevel(log.InfoLevel)
		logger.Level = lv
		logger.Formatter = customFormatter
	}

	logger.Out = os.Stdout

	return logger, nil
}

func Logger() *log.Logger {
	return logger
}

func Error(ctx context.Context, fields map[string]interface{}, format string, args ...interface{}) {
	if logger.Level >= log.ErrorLevel {
		entry := log.WithField("pid", os.Getpid())

		file, line, fName, err := extractCallerDetails()
		if err != nil {
			entry = entry.WithField("file", file).WithField("line", line).WithField("func", fName)
		}

		if ctx != nil {
			reqID := middleware.ContextRequestID(ctx)
			if reqID != "" {
				entry = entry.WithField("req_id", reqID)
			}
		}

		if len(args) > 0 {
			entry.WithFields(fields).Errorf(format, args)
		} else {
			entry.WithFields(fields).Errorln(format)
		}
	}
}

func Warn(ctx context.Context, fields map[string]interface{}, format string, args ...interface{}) {
	if logger.Level >= log.WarnLevel {
		entry := log.NewEntry(logger)

		file, _, fName, err := extractCallerDetails()
		if err != nil {
			entry = log.WithField("file", file).WithField("func", fName)
		}

		if ctx != nil {
			reqID := middleware.ContextRequestID(ctx)
			if reqID != "" {
				entry = entry.WithField("req_id", reqID)
			}
		}
		if len(args) > 0 {
			entry.WithFields(fields).Warnf(format, args...)
		} else {
			entry.WithFields(fields).Warnln(format)
		}
	}
}

func Info(ctx context.Context, fields map[string]interface{}, format string, args ...interface{}) {
	if logger.Level >= log.InfoLevel {
		entry := log.NewEntry(logger)

		if ctx != nil {
			reqID := middleware.ContextRequestID(ctx)
			if reqID != "" {
				entry = entry.WithField("req_id", reqID)
			}
		}

		if len(args) > 0 {
			entry.WithFields(fields).Infof(format, args...)
		} else {
			entry.WithFields(fields).Infoln(format)
		}
	}
}

func Panic(ctx context.Context, fields map[string]interface{}, format string, args ...interface{}) {
	if logger.Level >= log.ErrorLevel {
		entry := log.WithField("pid", os.Getpid())

		if ctx != nil {
			reqID := middleware.ContextRequestID(ctx)
			if reqID != "" {
				entry = entry.WithField("req_id", reqID)
			}
		}

		if len(args) > 0 {
			entry.WithFields(fields).Panicf(format, args)
		} else {
			entry.WithFields(fields).Panicln(format)
		}
	}
}

func Debug(ctx context.Context, fields map[string]interface{}, format string, args ...interface{}) {
	if logger.Level >= log.DebugLevel {
		entry := log.NewEntry(logger)

		if ctx != nil {
			reqID := middleware.ContextRequestID(ctx)
			if reqID != "" {
				entry = entry.WithField("req_id", reqID)
			}
		}

		if len(args) > 0 {
			entry.WithFields(fields).Debugf(format, args...)
		} else {
			entry.WithFields(fields).Debugln(format)
		}
	}
}

func extractCallerDetails() (string, int, string, error) {
	if pc, file, line, ok := runtime.Caller(2); ok {
		fName := runtime.FuncForPC(pc).Name()
		return file, line, fName, nil
	}

	return "", 0, "", errors.New("Unable to extract the caller details")
}
<|endoftext|>


<commit_before>// +build !windows

package log

import (
	"bytes"
	"fmt"
)

const (
	error_color   = "31"
	warn_color    = "33"
	success_color = "32"
	esc_start     = "\x1b["
	esc_stop      = "m"
	esc_default   = "0"
	GREEN         = "32"
	RED           = error_color
	YELLOW        = warn_color
)

var padded bool

func ColorString(in string, color string) string {
	var buf bytes.Buffer
	fmt.Fprintf(&buf, "%s%s%s", esc_start, color, esc_stop)
	fmt.Fprintf(&buf, in)
	fmt.Fprintf(&buf, "%s%s%s", esc_start, esc_default, esc_stop)
	return buf.String()
}

func Infof(format string, args ...interface{}) {
	println(format, "", false, args)
}

func Infoln(args ...interface{}) {
	println("", "", false, args)
}

func Successf(format string, args ...interface{}) {
	println(format, success_color, false, args)
}

func Successln(args ...interface{}) {
	println("", success_color, false, args)
}

func InfoToggle(on bool) {
}

func Errorln(args ...interface{}) {
	out := make([]interface{}, len(args)+1)
	out[0] = "ERROR:"
	copy(out[1:], args)
	println("", error_color, true, out)
}

func Errorf(format string, args ...interface{}) {
	println(fmt.Sprintf("ERROR: %s", format), error_color, true, args)
}

func Warnln(args ...interface{}) {
	out := make([]interface{}, len(args)+1)
	out[0] = "WARNING:"
	copy(out[1:], args)
	println("", warn_color, true, out)
}

func Warnf(format string, args ...interface{}) {
	println(fmt.Sprintf("WARNING: %s", format), warn_color, true, args)
}

func println(format string, color string, pad bool, args interface{}) {
	var buf bytes.Buffer
	if pad && !padded {
		buf.WriteByte('\n')
	}
	fmt.Fprintf(&buf, "%s%s%s", esc_start, color, esc_stop)
	if format == "" {
		fmt.Fprintln(&buf, (args.([]interface{}))...)
	} else {
		fmt.Fprintf(&buf, format, (args.([]interface{}))...)
	}
	fmt.Fprintf(&buf, "%s%s%s", esc_start, esc_default, esc_stop)
	if pad && !padded {
		buf.WriteByte('\n')
		padded = true
	} else {
		padded = false
	}
	fmt.Printf(buf.String())
}
<commit_msg>s/Printf/Print in log to prevent double-formatting<commit_after>// +build !windows

package log

import (
	"bytes"
	"fmt"
)

const (
	error_color   = "31"
	warn_color    = "33"
	success_color = "32"
	esc_start     = "\x1b["
	esc_stop      = "m"
	esc_default   = "0"
	GREEN         = "32"
	RED           = error_color
	YELLOW        = warn_color
)

var padded bool

func ColorString(in string, color string) string {
	var buf bytes.Buffer
	fmt.Fprintf(&buf, "%s%s%s", esc_start, color, esc_stop)
	fmt.Fprintf(&buf, in)
	fmt.Fprintf(&buf, "%s%s%s", esc_start, esc_default, esc_stop)
	return buf.String()
}

func Infof(format string, args ...interface{}) {
	println(format, "", false, args)
}

func Infoln(args ...interface{}) {
	println("", "", false, args)
}

func Successf(format string, args ...interface{}) {
	println(format, success_color, false, args)
}

func Successln(args ...interface{}) {
	println("", success_color, false, args)
}

func InfoToggle(on bool) {
}

func Errorln(args ...interface{}) {
	out := make([]interface{}, len(args)+1)
	out[0] = "ERROR:"
	copy(out[1:], args)
	println("", error_color, true, out)
}

func Errorf(format string, args ...interface{}) {
	println(fmt.Sprintf("ERROR: %s", format), error_color, true, args)
}

func Warnln(args ...interface{}) {
	out := make([]interface{}, len(args)+1)
	out[0] = "WARNING:"
	copy(out[1:], args)
	println("", warn_color, true, out)
}

func Warnf(format string, args ...interface{}) {
	println(fmt.Sprintf("WARNING: %s", format), warn_color, true, args)
}

func println(format string, color string, pad bool, args interface{}) {
	var buf bytes.Buffer
	if pad && !padded {
		buf.WriteByte('\n')
	}
	fmt.Fprintf(&buf, "%s%s%s", esc_start, color, esc_stop)
	if format == "" {
		fmt.Fprintln(&buf, (args.([]interface{}))...)
	} else {
		fmt.Fprintf(&buf, format, (args.([]interface{}))...)
	}
	fmt.Fprintf(&buf, "%s%s%s", esc_start, esc_default, esc_stop)
	if pad && !padded {
		buf.WriteByte('\n')
		padded = true
	} else {
		padded = false
	}
	fmt.Print(buf.String())
}
<|endoftext|>


<commit_before>package log

import (
	"encoding/json"
	"fmt"
	"path/filepath"
	"runtime"
	"strings"

	"github.com/astaxie/beego/logs"
	"github.com/btcboost/copernicus/conf"
)

const (
	defaultLogDirname = "logs"

	errModuleNotFound = "specified module not found"
)

var mapModule map[string]struct{}

func Print(module string, level string, format string, reason ...interface{}) {
	level = strings.ToLower(level)
	if isIncludeModule(module) {
		switch level {
		case "emergency":
			logs.Emergency(format, reason)
		case "alert":
			logs.Alert(format, reason)
		case "critical":
			logs.Critical(format, reason)
		case "error":
			logs.Error(format, reason)
		case "warn":
			logs.Warn(format, reason)
		case "info":
			logs.Info(format, reason)
		case "debug":
			logs.Debug(format, reason)
		case "notice":
			logs.Notice(format, reason)
		}
	} else {
		logs.GetLogger()
		logs.Error(errModuleNotFound)
	}
}

func isIncludeModule(module string) bool {
	module = strings.ToLower(module)
	if _, ok := mapModule[module]; ok {
		return true
	}
	return false
}

func init() {
	logDir := filepath.Join(conf.Cfg.DataDir, defaultLogDirname)

	logConf := struct {
		FileName string `json:"filename"`
		Level    int    `json:"level"`
		Daily    bool   `json:"daily"`
	}{
		FileName: logDir + "/" + conf.Cfg.Log.FileName,
		Level:    getLevel(conf.Cfg.Log.Level),
		Daily:    false,
	}

	configuration, err := json.Marshal(logConf)
	if err != nil {
		panic(err)
	}
	logs.SetLogger(logs.AdapterFile, string(configuration))

	// output filename and line number
	logs.EnableFuncCallDepth(true)
	logs.SetLogFuncCallDepth(4)
	// output async buffer
	logs.Async(1e3)

	// init mapModule
	mapModule = make(map[string]struct{})
	for _, module := range conf.Cfg.Log.Module {
		module = strings.ToLower(module)
		mapModule[module] = struct{}{}
	}
}

// Emergency logs a message at emergency level.
func Emergency(f interface{}, v ...interface{}) {
	logs.Emergency(f, v)
}

// Alert logs a message at alert level.
func Alert(f interface{}, v ...interface{}) {
	logs.Alert(f, v)
}

// Critical logs a message at critical level.
func Critical(f interface{}, v ...interface{}) {
	logs.Critical(f, v)
}

// Error logs a message at error level.
func Error(f interface{}, v ...interface{}) {
	logs.Error(f, v)
}

// Warning logs a message at warning level.
func Warning(f interface{}, v ...interface{}) {
	logs.Warning(f, v)
}

// Warn compatibility alias for Warning()
func Warn(f interface{}, v ...interface{}) {
	logs.Warn(f, v)
}

// Notice logs a message at notice level.
func Notice(f interface{}, v ...interface{}) {
	logs.Notice(f, v)
}

// Informational logs a message at info level.
func Informational(f interface{}, v ...interface{}) {
	logs.Informational(f, v)
}

// Info compatibility alias for Warning()
func Info(f interface{}, v ...interface{}) {
	logs.Info(f, v)
}

// Debug logs a message at debug level.
func Debug(f interface{}, v ...interface{}) {
	logs.Debug(f, v)
}

// Trace logs a message at trace level.
// compatibility alias for Warning()
func Trace(f interface{}, v ...interface{}) {
	logs.Trace(f, v)
}

func TraceLog() string {
	pc := make([]uintptr, 10) // at least 1 entry needed
	runtime.Callers(2, pc)
	f := runtime.FuncForPC(pc[0])
	_, line := f.FileLine(pc[0])
	return fmt.Sprintf("%s line : %d\n", f.Name(), line)
}

func GetLogger() *logs.BeeLogger {
	return logs.GetBeeLogger()
}
<commit_msg>log: updata log filename<commit_after>package log

import (
	"encoding/json"
	"fmt"
	"path/filepath"
	"runtime"
	"strings"

	"github.com/astaxie/beego/logs"
	"github.com/btcboost/copernicus/conf"
)

const (
	defaultLogDirname = "logs"

	errModuleNotFound = "specified module not found"
)

var mapModule map[string]struct{}

func Print(module string, level string, format string, reason ...interface{}) {
	level = strings.ToLower(level)
	if isIncludeModule(module) {
		switch level {
		case "emergency":
			logs.Emergency(format, reason)
		case "alert":
			logs.Alert(format, reason)
		case "critical":
			logs.Critical(format, reason)
		case "error":
			logs.Error(format, reason)
		case "warn":
			logs.Warn(format, reason)
		case "info":
			logs.Info(format, reason)
		case "debug":
			logs.Debug(format, reason)
		case "notice":
			logs.Notice(format, reason)
		}
	} else {
		logs.GetLogger()
		logs.Error(errModuleNotFound)
	}
}

func isIncludeModule(module string) bool {
	module = strings.ToLower(module)
	if _, ok := mapModule[module]; ok {
		return true
	}
	return false
}

func init() {
	logDir := filepath.Join(conf.Cfg.DataDir, defaultLogDirname)

	logConf := struct {
		FileName string `json:"filename"`
		Level    int    `json:"level"`
		Daily    bool   `json:"daily"`
	}{
		FileName: logDir + "/" + conf.Cfg.Log.FileName + ".log",
		Level:    getLevel(conf.Cfg.Log.Level),
		Daily:    false,
	}

	configuration, err := json.Marshal(logConf)
	if err != nil {
		panic(err)
	}
	logs.SetLogger(logs.AdapterFile, string(configuration))

	// output filename and line number
	logs.EnableFuncCallDepth(true)
	logs.SetLogFuncCallDepth(4)
	// output async buffer
	logs.Async(1e3)

	// init mapModule
	mapModule = make(map[string]struct{})
	for _, module := range conf.Cfg.Log.Module {
		module = strings.ToLower(module)
		mapModule[module] = struct{}{}
	}
}

// Emergency logs a message at emergency level.
func Emergency(f interface{}, v ...interface{}) {
	logs.Emergency(f, v)
}

// Alert logs a message at alert level.
func Alert(f interface{}, v ...interface{}) {
	logs.Alert(f, v)
}

// Critical logs a message at critical level.
func Critical(f interface{}, v ...interface{}) {
	logs.Critical(f, v)
}

// Error logs a message at error level.
func Error(f interface{}, v ...interface{}) {
	logs.Error(f, v)
}

// Warning logs a message at warning level.
func Warning(f interface{}, v ...interface{}) {
	logs.Warning(f, v)
}

// Warn compatibility alias for Warning()
func Warn(f interface{}, v ...interface{}) {
	logs.Warn(f, v)
}

// Notice logs a message at notice level.
func Notice(f interface{}, v ...interface{}) {
	logs.Notice(f, v)
}

// Informational logs a message at info level.
func Informational(f interface{}, v ...interface{}) {
	logs.Informational(f, v)
}

// Info compatibility alias for Warning()
func Info(f interface{}, v ...interface{}) {
	logs.Info(f, v)
}

// Debug logs a message at debug level.
func Debug(f interface{}, v ...interface{}) {
	logs.Debug(f, v)
}

// Trace logs a message at trace level.
// compatibility alias for Warning()
func Trace(f interface{}, v ...interface{}) {
	logs.Trace(f, v)
}

func TraceLog() string {
	pc := make([]uintptr, 10) // at least 1 entry needed
	runtime.Callers(2, pc)
	f := runtime.FuncForPC(pc[0])
	_, line := f.FileLine(pc[0])
	return fmt.Sprintf("%s line : %d\n", f.Name(), line)
}

func GetLogger() *logs.BeeLogger {
	return logs.GetBeeLogger()
}
<|endoftext|>


<commit_before>package log

import (
	"fmt"

	"bitbucket.org/mundipagg/boletoapi/config"
	"github.com/mundipagg/goseq"
)

var logger *goseq.Logger

// Operation a operacao usada na API
var Operation string

// Recipient o nome do banco
var Recipient string

// Log struct com os elemtos do log
type Log struct {
	Operation   string
	Recipient   string
	NossoNumero uint
	logger      *goseq.Logger
}

//Install instala o "servico" de log do SEQ
func Install() error {
	_logger, err := goseq.GetLogger(config.Get().SEQUrl, config.Get().SEQAPIKey, 150)
	if err != nil {
		return err
	}
	_logger.SetDefaultProperties(map[string]interface{}{
		"Application": config.Get().ApplicationName,
		"Environment": config.Get().Environment,
		"Domain":      config.Get().SEQDomain,
	})
	logger = _logger
	return nil
}

func formatter(message string) string {
	return "[{Application}: {Operation}] - {MessageType} " + message
}

//CreateLog cria uma nova instancia do Log
func CreateLog() *Log {
	return &Log{
		logger: logger,
	}
}

// Request loga o request para algum banco
func (l Log) Request(content interface{}, url string, headers map[string]string) {
	go (func() {
		props := l.defaultProperties("Request", content)
		props.AddProperty("Headers", headers)
		props.AddProperty("URL", url)
		msg := formatter("to {Recipient} ({URL})")

		l.logger.Information(msg, props)
	})()
}

// Response loga o response para algum banco
func (l Log) Response(content interface{}, url string) {
	go (func() {
		props := l.defaultProperties("Response", content)
		props.AddProperty("URL", url)
		msg := formatter("from {Recipient} ({URL})")

		l.logger.Information(msg, props)
	})()
}

//Info loga mensagem do level INFO
func Info(msg string) {
	go logger.Information(msg, goseq.NewProperties())
}

//Warn loga mensagem do leve Warning
func (l Log) Warn(content interface{}, msg string) {
	go (func() {
		props := l.defaultProperties("Warning", content)
		m := formatter(msg)

		l.logger.Warning(m, props)
	})()
}

// Fatal loga erros da aplicação
func (l Log) Fatal(content interface{}, msg string) {
	go (func() {
		props := l.defaultProperties("Error", content)
		m := formatter(msg)

		l.logger.Fatal(m, props)
	})()
}

func (l Log) defaultProperties(messageType string, content interface{}) goseq.Properties {
	props := goseq.NewProperties()
	props.AddProperty("MessageType", messageType)
	props.AddProperty("Content", content)
	props.AddProperty("Recipient", l.Recipient)
	props.AddProperty("Operation", l.Operation)
	props.AddProperty("NossoNumero", l.NossoNumero)
	return props
}

//Close fecha a conexao com o SEQ
func Close() {
	fmt.Println("Closing SEQ Connection")
	logger.Close()
}
<commit_msg>:art: muda o numero de consumers do log para 100 ao inves de 150 para diminuir o gargalo de saida de requisição para o seq<commit_after>package log

import (
	"fmt"

	"bitbucket.org/mundipagg/boletoapi/config"
	"github.com/mundipagg/goseq"
)

var logger *goseq.Logger

// Operation a operacao usada na API
var Operation string

// Recipient o nome do banco
var Recipient string

// Log struct com os elemtos do log
type Log struct {
	Operation   string
	Recipient   string
	NossoNumero uint
	logger      *goseq.Logger
}

//Install instala o "servico" de log do SEQ
func Install() error {
	_logger, err := goseq.GetLogger(config.Get().SEQUrl, config.Get().SEQAPIKey, 100)
	if err != nil {
		return err
	}
	_logger.SetDefaultProperties(map[string]interface{}{
		"Application": config.Get().ApplicationName,
		"Environment": config.Get().Environment,
		"Domain":      config.Get().SEQDomain,
	})
	logger = _logger
	return nil
}

func formatter(message string) string {
	return "[{Application}: {Operation}] - {MessageType} " + message
}

//CreateLog cria uma nova instancia do Log
func CreateLog() *Log {
	return &Log{
		logger: logger,
	}
}

// Request loga o request para algum banco
func (l Log) Request(content interface{}, url string, headers map[string]string) {
	go (func() {
		props := l.defaultProperties("Request", content)
		props.AddProperty("Headers", headers)
		props.AddProperty("URL", url)
		msg := formatter("to {Recipient} ({URL})")

		l.logger.Information(msg, props)
	})()
}

// Response loga o response para algum banco
func (l Log) Response(content interface{}, url string) {
	go (func() {
		props := l.defaultProperties("Response", content)
		props.AddProperty("URL", url)
		msg := formatter("from {Recipient} ({URL})")

		l.logger.Information(msg, props)
	})()
}

//Info loga mensagem do level INFO
func Info(msg string) {
	go logger.Information(msg, goseq.NewProperties())
}

//Warn loga mensagem do leve Warning
func (l Log) Warn(content interface{}, msg string) {
	go (func() {
		props := l.defaultProperties("Warning", content)
		m := formatter(msg)

		l.logger.Warning(m, props)
	})()
}

// Fatal loga erros da aplicação
func (l Log) Fatal(content interface{}, msg string) {
	go (func() {
		props := l.defaultProperties("Error", content)
		m := formatter(msg)

		l.logger.Fatal(m, props)
	})()
}

func (l Log) defaultProperties(messageType string, content interface{}) goseq.Properties {
	props := goseq.NewProperties()
	props.AddProperty("MessageType", messageType)
	props.AddProperty("Content", content)
	props.AddProperty("Recipient", l.Recipient)
	props.AddProperty("Operation", l.Operation)
	props.AddProperty("NossoNumero", l.NossoNumero)
	return props
}

//Close fecha a conexao com o SEQ
func Close() {
	fmt.Println("Closing SEQ Connection")
	logger.Close()
}
<|endoftext|>


<commit_before>package gop

import (
	"errors"
	"fmt"
	"log"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/cocoonlife/timber"
)

type LogFormatterFactory interface {
	Create() timber.LogFormatter
}

type TimberLogFormatterFactory struct {
}

func (t *TimberLogFormatterFactory) Create() timber.LogFormatter {
	return timber.NewJSONFormatter()
}

type Logger timber.Logger

func string2Level(logLevelStr string) (timber.Level, error) {
	logLevelStr = strings.ToUpper(logLevelStr)
	for logLevel, levelStr := range timber.LongLevelStrings {
		if logLevelStr == levelStr {
			return timber.Level(logLevel), nil
		}
	}
	return 0, errors.New("Not found")
}

func (a *App) makeConfigLogger() (timber.ConfigLogger, bool) {
	defaultLogPattern := "[%D %T] [%L] %M"
	filenamesByDefault, _ := a.Cfg.GetBool("gop", "log_filename", false)
	if filenamesByDefault {
		defaultLogPattern = "[%D %T] [%L] %s %M"
	}
	logPattern, _ := a.Cfg.Get("gop", "log_pattern", defaultLogPattern)

	// If set, hack all logging to stdout for dev
	forceStdout, _ := a.Cfg.GetBool("gop", "stdout_only_logging", false)
	configLogger := timber.ConfigLogger{
		LogWriter: new(timber.ConsoleWriter),
		Level:     timber.INFO,
		Formatter: timber.NewPatFormatter(logPattern),
	}

	defaultLogDir, _ := a.Cfg.Get("gop", "log_dir", "/var/log")
	fellbackToCWD := false
	a.logDir = defaultLogDir + "/" + a.ProjectName
	if !forceStdout {
		defaultLogFname := a.logDir + "/" + a.AppName + ".log"
		logFname, _ := a.Cfg.Get("gop", "log_file", defaultLogFname)

		newWriter, err := timber.NewFileWriter(logFname)
		_, dirExistsErr := os.Stat(a.logDir)
		if dirExistsErr != nil && os.IsNotExist(dirExistsErr) {
			// Carry on with stdout logging, but remember to mention it
			fellbackToCWD = true
			a.logDir = "."
		} else {
			if err != nil {
				panic(fmt.Sprintf("Can't open log file: %s", err))
			}
			configLogger.LogWriter = newWriter
		}
	}

	logLevelStr, _ := a.Cfg.Get("gop", "log_level", "INFO")
	logLevel, err := string2Level(logLevelStr)
	if err == nil {
		configLogger.Level = timber.Level(logLevel)
	}

	granularsPrefix, _ := a.Cfg.Get("gop", "log_granulars_prefix", "")
	granularsStrs, _ := a.Cfg.GetList("gop", "log_granulars", nil)
	if granularsStrs != nil {
		configLogger.Granulars = make(map[string]timber.Level)
	GRANULARS:
		for _, granStr := range granularsStrs {
			bits := strings.Split(granStr, ":")
			if len(bits) != 2 {
				continue GRANULARS
			}
			pkgPart := bits[0]
			pkgLevel := bits[1]

			if pkgPart == "" || pkgLevel == "" {
				continue GRANULARS
			}
			pkgName := pkgPart
			if granularsPrefix != "" {
				pkgName = granularsPrefix + "/" + pkgPart
			}
			logLevel, err := string2Level(pkgLevel)
			if err == nil {
				configLogger.Granulars[pkgName] = logLevel
			}
		}
	}

	return configLogger, fellbackToCWD
}

func (a *App) setLogger(name string, logger timber.ConfigLogger) {
	l := timber.Global
	if i, ok := a.loggerMap[name]; ok {
		l.SetLogger(i, logger)
	} else {
		a.loggerMap[name] = l.AddLogger(logger)
	}
}

func (a *App) initLogging() {
	// *Don't* create a NewTImber here. Logs are only flushed on Close() and if we
	// have more than one timber, it's easy to only Close() one of them...
	l := timber.Global
	a.Logger = l

	// Set up the default go logger to go here too, so 3rd party
	// module logging plays nicely
	log.SetFlags(0)
	log.SetOutput(l)

	a.configureLogging()
	a.Cfg.AddOnChangeCallback(func(cfg *Config) { a.configureLogging() })
}

func (a *App) configureLogging() {
	l := timber.Global

	configLogger, fellbackToCWD := a.makeConfigLogger()
	a.setLogger("configLogger", configLogger)
	if fellbackToCWD {
		l.Error("Logging directory does not exist - logging to stdout")
	}

	doAccessLog, _ := a.Cfg.GetBool("gop", "access_log_enable", false)
	if doAccessLog {
		defaultAccessLogFname := a.logDir + "/" + a.AppName + "-access.log"
		accessLogFilename, _ := a.Cfg.Get("gop", "access_log_filename", defaultAccessLogFname)
		// Don't use .Create since it truncates
		var err error
		a.accessLog, err = os.OpenFile(accessLogFilename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666)
		if err != nil {
			l.Errorf("Can't open access log; %s", err.Error())
		}
	}

	// logentries logging service
	if token, ok := a.Cfg.Get("gop", "log_logentries_token", ""); ok {
		if le, err := NewLogEntriesWriter(token); err == nil {
			logger := timber.ConfigLogger{
				LogWriter: le,
				Level:     timber.DEBUG,
				Formatter: a.logFormatterFactory.Create(),
			}
			a.setLogger("logentries", logger)
			l.Infof("Added Logentries logger")
		} else {
			l.Errorf("Error creating logentries client: %s", err.Error())
		}
	}

	// Loggly logging service
	if token, ok := a.Cfg.Get("gop", "log_loggly_token", ""); ok {
		tags := []string{a.ProjectName, a.AppName, a.Hostname()}
		if lw, err := NewLogglyWriter(token, tags...); err == nil {
			logger := timber.ConfigLogger{
				LogWriter: lw,
				Level:     timber.DEBUG,
				Formatter: a.logFormatterFactory.Create(),
			}
			a.setLogger("loggly", logger)
			l.Infof("Added Loggly logger with tags:%s", tags)
		} else {
			l.Errorf("Error creating loggly client: %s", err.Error())
		}
	}
}

func (a *App) closeLogging() {
	if a.accessLog != nil {
		err := a.accessLog.Close()
		if err != nil {
			a.Errorf("Error closing access log: %s", err.Error())
		}
	}
	timber.Close()
}

func (a *App) WriteAccessLog(req *Req, dur time.Duration) {
	if a.accessLog == nil {
		return
	}
	logEvery, _ := a.Cfg.GetInt("gop", "access_log_every", 0)
	if logEvery > 0 {
		a.suppressedAccessLogLines++
		if a.suppressedAccessLogLines < logEvery {
			a.Debug("Suppressing access log line [%d/%d]", a.suppressedAccessLogLines, logEvery)
			return
		}
	}
	a.suppressedAccessLogLines = 0

	// Copy an nginx-log access log
	/* ---
	   gaiadev.leedsdev.net 0.022 192.168.111.1 - - [05/Feb/2014:13:39:22 +0000] "GET /bby/sso/login?next_url=https%3A%2F%2Fgaiadev.leedsdev.net%2F HTTP/1.1" 302 0 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:26.0) Gecko/20100101 Firefox/26.0"
	   --- */
	trimPort := func(s string) string {
		colonOffset := strings.IndexByte(s, ':')
		if colonOffset >= 0 {
			s = s[:colonOffset]
		}
		return s
	}
	quote := func(s string) string {
		return string(strconv.AppendQuote([]byte{}, s))
	}

	reqFirstLine := fmt.Sprintf("%s %s %s", req.R.Method, req.R.RequestURI, req.R.Proto)
	referrerLine := req.R.Referer()
	if referrerLine == "" {
		referrerLine = "-"
	}
	uaLine := req.R.Header.Get("User-Agent")
	if uaLine == "" {
		uaLine = "-"
	}
	hostname := a.Hostname()
	logLine := fmt.Sprintf("%s %.3f %s %s %s %s %s %d %d %s %s\n",
		hostname,
		dur.Seconds(),
		trimPort(req.RealRemoteIP),
		"-", // Ident <giggle>
		"-", // user
		//		req.startTime.Format("[02/Jan/2006:15:04:05 -0700]"),
		req.startTime.Format("["+time.RFC3339+"]"),
		quote(reqFirstLine),
		req.W.code,
		req.W.size,
		quote(referrerLine),
		quote(uaLine))
	_, err := req.app.accessLog.WriteString(logLine)
	if err != nil {
		a.Errorf("Failed to write to access log: %s", err.Error())
	}
}
<commit_msg>logging.go: avoid resource leak in fall-back<commit_after>package gop

import (
	"errors"
	"fmt"
	"log"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/cocoonlife/timber"
)

type LogFormatterFactory interface {
	Create() timber.LogFormatter
}

type TimberLogFormatterFactory struct {
}

func (t *TimberLogFormatterFactory) Create() timber.LogFormatter {
	return timber.NewJSONFormatter()
}

type Logger timber.Logger

func string2Level(logLevelStr string) (timber.Level, error) {
	logLevelStr = strings.ToUpper(logLevelStr)
	for logLevel, levelStr := range timber.LongLevelStrings {
		if logLevelStr == levelStr {
			return timber.Level(logLevel), nil
		}
	}
	return 0, errors.New("Not found")
}

func (a *App) makeConfigLogger() (timber.ConfigLogger, bool) {
	defaultLogPattern := "[%D %T] [%L] %M"
	filenamesByDefault, _ := a.Cfg.GetBool("gop", "log_filename", false)
	if filenamesByDefault {
		defaultLogPattern = "[%D %T] [%L] %s %M"
	}
	logPattern, _ := a.Cfg.Get("gop", "log_pattern", defaultLogPattern)

	// If set, hack all logging to stdout for dev
	forceStdout, _ := a.Cfg.GetBool("gop", "stdout_only_logging", false)
	configLogger := timber.ConfigLogger{
		LogWriter: new(timber.ConsoleWriter),
		Level:     timber.INFO,
		Formatter: timber.NewPatFormatter(logPattern),
	}

	defaultLogDir, _ := a.Cfg.Get("gop", "log_dir", "/var/log")
	fellbackToCWD := false
	a.logDir = defaultLogDir + "/" + a.ProjectName
	if !forceStdout {
		defaultLogFname := a.logDir + "/" + a.AppName + ".log"
		logFname, _ := a.Cfg.Get("gop", "log_file", defaultLogFname)

		_, dirExistsErr := os.Stat(a.logDir)
		if dirExistsErr != nil && os.IsNotExist(dirExistsErr) {
			// Carry on with stdout logging, but remember to mention it
			fellbackToCWD = true
			a.logDir = "."
		} else {
			newWriter, err := timber.NewFileWriter(logFname)
			if err != nil {
				panic(fmt.Sprintf("Can't open log file: %s", err))
			}
			configLogger.LogWriter = newWriter
		}
	}

	logLevelStr, _ := a.Cfg.Get("gop", "log_level", "INFO")
	logLevel, err := string2Level(logLevelStr)
	if err == nil {
		configLogger.Level = timber.Level(logLevel)
	}

	granularsPrefix, _ := a.Cfg.Get("gop", "log_granulars_prefix", "")
	granularsStrs, _ := a.Cfg.GetList("gop", "log_granulars", nil)
	if granularsStrs != nil {
		configLogger.Granulars = make(map[string]timber.Level)
	GRANULARS:
		for _, granStr := range granularsStrs {
			bits := strings.Split(granStr, ":")
			if len(bits) != 2 {
				continue GRANULARS
			}
			pkgPart := bits[0]
			pkgLevel := bits[1]

			if pkgPart == "" || pkgLevel == "" {
				continue GRANULARS
			}
			pkgName := pkgPart
			if granularsPrefix != "" {
				pkgName = granularsPrefix + "/" + pkgPart
			}
			logLevel, err := string2Level(pkgLevel)
			if err == nil {
				configLogger.Granulars[pkgName] = logLevel
			}
		}
	}

	return configLogger, fellbackToCWD
}

func (a *App) setLogger(name string, logger timber.ConfigLogger) {
	l := timber.Global
	if i, ok := a.loggerMap[name]; ok {
		l.SetLogger(i, logger)
	} else {
		a.loggerMap[name] = l.AddLogger(logger)
	}
}

func (a *App) initLogging() {
	// *Don't* create a NewTImber here. Logs are only flushed on Close() and if we
	// have more than one timber, it's easy to only Close() one of them...
	l := timber.Global
	a.Logger = l

	// Set up the default go logger to go here too, so 3rd party
	// module logging plays nicely
	log.SetFlags(0)
	log.SetOutput(l)

	a.configureLogging()
	a.Cfg.AddOnChangeCallback(func(cfg *Config) { a.configureLogging() })
}

func (a *App) configureLogging() {
	l := timber.Global

	configLogger, fellbackToCWD := a.makeConfigLogger()
	a.setLogger("configLogger", configLogger)
	if fellbackToCWD {
		l.Error("Logging directory does not exist - logging to stdout")
	}

	doAccessLog, _ := a.Cfg.GetBool("gop", "access_log_enable", false)
	if doAccessLog {
		defaultAccessLogFname := a.logDir + "/" + a.AppName + "-access.log"
		accessLogFilename, _ := a.Cfg.Get("gop", "access_log_filename", defaultAccessLogFname)
		// Don't use .Create since it truncates
		var err error
		a.accessLog, err = os.OpenFile(accessLogFilename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666)
		if err != nil {
			l.Errorf("Can't open access log; %s", err.Error())
		}
	}

	// logentries logging service
	if token, ok := a.Cfg.Get("gop", "log_logentries_token", ""); ok {
		if le, err := NewLogEntriesWriter(token); err == nil {
			logger := timber.ConfigLogger{
				LogWriter: le,
				Level:     timber.DEBUG,
				Formatter: a.logFormatterFactory.Create(),
			}
			a.setLogger("logentries", logger)
			l.Infof("Added Logentries logger")
		} else {
			l.Errorf("Error creating logentries client: %s", err.Error())
		}
	}

	// Loggly logging service
	if token, ok := a.Cfg.Get("gop", "log_loggly_token", ""); ok {
		tags := []string{a.ProjectName, a.AppName, a.Hostname()}
		if lw, err := NewLogglyWriter(token, tags...); err == nil {
			logger := timber.ConfigLogger{
				LogWriter: lw,
				Level:     timber.DEBUG,
				Formatter: a.logFormatterFactory.Create(),
			}
			a.setLogger("loggly", logger)
			l.Infof("Added Loggly logger with tags:%s", tags)
		} else {
			l.Errorf("Error creating loggly client: %s", err.Error())
		}
	}
}

func (a *App) closeLogging() {
	if a.accessLog != nil {
		err := a.accessLog.Close()
		if err != nil {
			a.Errorf("Error closing access log: %s", err.Error())
		}
	}
	timber.Close()
}

func (a *App) WriteAccessLog(req *Req, dur time.Duration) {
	if a.accessLog == nil {
		return
	}
	logEvery, _ := a.Cfg.GetInt("gop", "access_log_every", 0)
	if logEvery > 0 {
		a.suppressedAccessLogLines++
		if a.suppressedAccessLogLines < logEvery {
			a.Debug("Suppressing access log line [%d/%d]", a.suppressedAccessLogLines, logEvery)
			return
		}
	}
	a.suppressedAccessLogLines = 0

	// Copy an nginx-log access log
	/* ---
	   gaiadev.leedsdev.net 0.022 192.168.111.1 - - [05/Feb/2014:13:39:22 +0000] "GET /bby/sso/login?next_url=https%3A%2F%2Fgaiadev.leedsdev.net%2F HTTP/1.1" 302 0 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:26.0) Gecko/20100101 Firefox/26.0"
	   --- */
	trimPort := func(s string) string {
		colonOffset := strings.IndexByte(s, ':')
		if colonOffset >= 0 {
			s = s[:colonOffset]
		}
		return s
	}
	quote := func(s string) string {
		return string(strconv.AppendQuote([]byte{}, s))
	}

	reqFirstLine := fmt.Sprintf("%s %s %s", req.R.Method, req.R.RequestURI, req.R.Proto)
	referrerLine := req.R.Referer()
	if referrerLine == "" {
		referrerLine = "-"
	}
	uaLine := req.R.Header.Get("User-Agent")
	if uaLine == "" {
		uaLine = "-"
	}
	hostname := a.Hostname()
	logLine := fmt.Sprintf("%s %.3f %s %s %s %s %s %d %d %s %s\n",
		hostname,
		dur.Seconds(),
		trimPort(req.RealRemoteIP),
		"-", // Ident <giggle>
		"-", // user
		//		req.startTime.Format("[02/Jan/2006:15:04:05 -0700]"),
		req.startTime.Format("["+time.RFC3339+"]"),
		quote(reqFirstLine),
		req.W.code,
		req.W.size,
		quote(referrerLine),
		quote(uaLine))
	_, err := req.app.accessLog.WriteString(logLine)
	if err != nil {
		a.Errorf("Failed to write to access log: %s", err.Error())
	}
}
<|endoftext|>


<commit_before>package glock

import (
	"errors"
	"io/ioutil"
	"log"
	"time"
)

// LockManager manages all the locks for a single client
type LockManager struct {
	Logger *log.Logger
	opts   AcquireOptions
	client Client
	locks  map[string]Lock
	hb     map[string]chan error
}

// AcquireOptions allows to set options during lock acquisition.
type AcquireOptions struct {
	// TTL is the ttl of the lock. If <= 0 the manager defaultTTL is used
	TTL time.Duration
	// How long to wait for the lock to be acquired.
	MaxWait time.Duration
	// The Data to set with the lock.
	Data string
}

// NewLockManager returns a new LockManager for the given client.
// By default, logging is sent to /dev/null. You must call SetOutput() on
// the Logger instance if you want logging to be sent somewhere.
func NewLockManager(client Client, opts AcquireOptions) *LockManager {
	return &LockManager{
		log.New(ioutil.Discard, "glock: ", log.LstdFlags|log.LUTC),
		opts,
		client,
		make(map[string]Lock),
		make(map[string]chan error),
	}
}

// SetData updates data for an existing, acquired lock. Data won't be
// saved into the backend database until the lock is refreshed (either manually
// or at the next heartbeat.
func (m *LockManager) SetData(lock, data string) error {
	l, ok := m.locks[lock]
	if !ok {
		return ErrInvalidLock
	}
	l.SetData(data)
	return nil
}

// Client returns the current lock client in use
func (m *LockManager) Client() Client {
	return m.client
}

// Info returns information about a lock with the given name
func (m *LockManager) Info(lockName string) (*LockInfo, error) {
	lock, ok := m.locks[lockName]
	if !ok {
		return nil, ErrInvalidLock
	}
	info, err := lock.Info()
	if err != nil {
		return nil, err
	}
	return info, nil
}

// Refresh refreshes a single lock.
func (m *LockManager) Refresh(lockName string) error {
	lock, ok := m.locks[lockName]
	if !ok {
		return ErrInvalidLock
	}
	return lock.Refresh()
}

func (m *LockManager) acquire(lockName string, opts AcquireOptions) error {
	lock := m.client.NewLock(lockName)
	if opts.Data != "" {
		lock.SetData(opts.Data)
	}
	err := lock.Acquire(opts.TTL)
	if err != nil {
		m.Logger.Printf("client %s: Cannot acquire lock '%s': %s",
			m.client.ID(), lockName, err.Error())
		return err
	}
	m.Logger.Printf("client %s: Acquired lock '%s' for %v", m.client.ID(), lockName, opts.TTL)
	m.locks[lockName] = lock
	return nil
}

// Acquire tries to acquire the lock with the given name using the default TTL
// for the manager. If the lock cannot be acquired, it will wait up to MaxWait
// for the lock to be released by the owner.
// If this manager instance already has acquired this lock, this action is a no-op.
func (m *LockManager) Acquire(lockName string, opts AcquireOptions) error {

	var waited time.Duration
	lock := m.client.NewLock(lockName)

	if opts.TTL <= 0 {
		opts.TTL = m.opts.TTL
	}

	if opts.Data == "" {
		opts.Data = m.opts.Data
	}

	if opts.MaxWait <= 0 {
		opts.MaxWait = m.opts.MaxWait
	}

	if lock, ok := m.locks[lockName]; ok {
		lock.SetData(opts.Data)
		return lock.RefreshTTL(opts.TTL)
	}

	for {
		init := time.Now()
		err := m.acquire(lockName, opts)
		if err == nil {
			return nil
		}
		if err != ErrLockHeldByOtherClient {
			return err
		}
		info, err := lock.Info()
		if err != nil {
			return err
		}

		if waited >= opts.MaxWait {
			m.Logger.Printf("client %s: Cannot acquire lock '%s' after %v",
				m.client.ID(), lockName, waited)
			return ErrLockHeldByOtherClient
		}

		wait := info.TTL - time.Since(init)
		if waited+wait > opts.MaxWait {
			wait = opts.MaxWait - waited
		}

		time.Sleep(wait)
		waited = waited + wait
	}
}

// Release releases a lock with the given name. The lock must be held by the current manager.
// Any eventual heartbeating will be stopped as well.
func (m *LockManager) Release(lockName string) error {
	var err error
	m.Logger.Printf("client %s: Releasing lock '%s'", m.client.ID(), lockName)
	if lock, ok := m.locks[lockName]; ok {
		m.StopHeartbeat(lockName)
		err = lock.Release()
		delete(m.locks, lockName)
	}
	return err
}

// ReleaseAll releases all the locks held by the manager.
func (m *LockManager) ReleaseAll() map[string]error {
	results := make(map[string]error)
	var err error
	for n := range m.locks {
		err = m.Release(n)
		if err != nil {
			results[n] = err
		}
	}
	return results
}

func heartbeat(client Client, logger *log.Logger, lockName string, ttl time.Duration, control chan error) {
	client.Reconnect()
	defer client.Close()
	freq := time.Duration(ttl / 2)
	enlapsed := time.Duration(0)
	sleeptime := 10 * time.Millisecond
	if sleeptime > freq {
		sleeptime = freq
	}

	lock := client.NewLock(lockName)
	for {
		select {
		case <-control:
			return

		default:
			if enlapsed >= freq {
				start := time.Now()
				err := lock.RefreshTTL(ttl)
				if err != nil {
					logger.Printf("client %s: heartbeat -- FATAL cannot refresh lock '%s': %s",
						client.ID(), lockName, err.Error())
					select {
					case control <- err:
						return
					case <-time.After(sleeptime):
						panic(err)
					}
				}
				logger.Printf("client %s: heartbeat -- refreshed lock '%s' for %v",
					client.ID(), lockName, ttl)
				s := sleeptime - (time.Now().Sub(start))
				time.Sleep(s)
				enlapsed = s
			} else {
				time.Sleep(sleeptime)
				enlapsed += sleeptime
			}
		}
	}
}

// StartHeartbeat starts a goroutine in the backgroud that will refresh the
// lock The lock will be refreshed every ttl/2 or whichever is greater.  The
// background goroutine will panic() if the lock cannot be refreshed for any
// reason The background goroutine will run forever until StopHeartbeat is
// called or the lock released.  It will return a channel to signal if the lock
// cannot be refreshed during heartbeats (before panicking)
func (m *LockManager) StartHeartbeat(lockName string) (<-chan error, error) {
	info, err := m.Info(lockName)
	if err != nil {
		return nil, err
	}
	m.Logger.Printf("client %s: Starting heartbeats for lock '%s' every %v", m.client.ID(),
		lockName, info.TTL/2)
	m.hb[lockName] = make(chan error)
	go heartbeat(m.client.Clone(), m.Logger, lockName, info.TTL, m.hb[lockName])
	return m.hb[lockName], nil
}

// StopHeartbeat will stop the background gororoutine, if any, that is heartbeating the given lock
func (m *LockManager) StopHeartbeat(lockName string) {
	if c, ok := m.hb[lockName]; ok {
		m.Logger.Printf("client %s: Stopping heartbeats for lock '%s'", m.client.ID(),
			lockName)
		c <- errors.New("")
		close(c)
		delete(m.hb, lockName)
	}
}
<commit_msg>Elapsed, not enlapsed<commit_after>package glock

import (
	"errors"
	"io/ioutil"
	"log"
	"time"
)

// LockManager manages all the locks for a single client
type LockManager struct {
	Logger *log.Logger
	opts   AcquireOptions
	client Client
	locks  map[string]Lock
	hb     map[string]chan error
}

// AcquireOptions allows to set options during lock acquisition.
type AcquireOptions struct {
	// TTL is the ttl of the lock. If <= 0 the manager defaultTTL is used
	TTL time.Duration
	// How long to wait for the lock to be acquired.
	MaxWait time.Duration
	// The Data to set with the lock.
	Data string
}

// NewLockManager returns a new LockManager for the given client.
// By default, logging is sent to /dev/null. You must call SetOutput() on
// the Logger instance if you want logging to be sent somewhere.
func NewLockManager(client Client, opts AcquireOptions) *LockManager {
	return &LockManager{
		log.New(ioutil.Discard, "glock: ", log.LstdFlags|log.LUTC),
		opts,
		client,
		make(map[string]Lock),
		make(map[string]chan error),
	}
}

// SetData updates data for an existing, acquired lock. Data won't be
// saved into the backend database until the lock is refreshed (either manually
// or at the next heartbeat.
func (m *LockManager) SetData(lock, data string) error {
	l, ok := m.locks[lock]
	if !ok {
		return ErrInvalidLock
	}
	l.SetData(data)
	return nil
}

// Client returns the current lock client in use
func (m *LockManager) Client() Client {
	return m.client
}

// Info returns information about a lock with the given name
func (m *LockManager) Info(lockName string) (*LockInfo, error) {
	lock, ok := m.locks[lockName]
	if !ok {
		return nil, ErrInvalidLock
	}
	info, err := lock.Info()
	if err != nil {
		return nil, err
	}
	return info, nil
}

// Refresh refreshes a single lock.
func (m *LockManager) Refresh(lockName string) error {
	lock, ok := m.locks[lockName]
	if !ok {
		return ErrInvalidLock
	}
	return lock.Refresh()
}

func (m *LockManager) acquire(lockName string, opts AcquireOptions) error {
	lock := m.client.NewLock(lockName)
	if opts.Data != "" {
		lock.SetData(opts.Data)
	}
	err := lock.Acquire(opts.TTL)
	if err != nil {
		m.Logger.Printf("client %s: Cannot acquire lock '%s': %s",
			m.client.ID(), lockName, err.Error())
		return err
	}
	m.Logger.Printf("client %s: Acquired lock '%s' for %v", m.client.ID(), lockName, opts.TTL)
	m.locks[lockName] = lock
	return nil
}

// Acquire tries to acquire the lock with the given name using the default TTL
// for the manager. If the lock cannot be acquired, it will wait up to MaxWait
// for the lock to be released by the owner.
// If this manager instance already has acquired this lock, this action is a no-op.
func (m *LockManager) Acquire(lockName string, opts AcquireOptions) error {

	var waited time.Duration
	lock := m.client.NewLock(lockName)

	if opts.TTL <= 0 {
		opts.TTL = m.opts.TTL
	}

	if opts.Data == "" {
		opts.Data = m.opts.Data
	}

	if opts.MaxWait <= 0 {
		opts.MaxWait = m.opts.MaxWait
	}

	if lock, ok := m.locks[lockName]; ok {
		lock.SetData(opts.Data)
		return lock.RefreshTTL(opts.TTL)
	}

	for {
		init := time.Now()
		err := m.acquire(lockName, opts)
		if err == nil {
			return nil
		}
		if err != ErrLockHeldByOtherClient {
			return err
		}
		info, err := lock.Info()
		if err != nil {
			return err
		}

		if waited >= opts.MaxWait {
			m.Logger.Printf("client %s: Cannot acquire lock '%s' after %v",
				m.client.ID(), lockName, waited)
			return ErrLockHeldByOtherClient
		}

		wait := info.TTL - time.Since(init)
		if waited+wait > opts.MaxWait {
			wait = opts.MaxWait - waited
		}

		time.Sleep(wait)
		waited = waited + wait
	}
}

// Release releases a lock with the given name. The lock must be held by the current manager.
// Any eventual heartbeating will be stopped as well.
func (m *LockManager) Release(lockName string) error {
	var err error
	m.Logger.Printf("client %s: Releasing lock '%s'", m.client.ID(), lockName)
	if lock, ok := m.locks[lockName]; ok {
		m.StopHeartbeat(lockName)
		err = lock.Release()
		delete(m.locks, lockName)
	}
	return err
}

// ReleaseAll releases all the locks held by the manager.
func (m *LockManager) ReleaseAll() map[string]error {
	results := make(map[string]error)
	var err error
	for n := range m.locks {
		err = m.Release(n)
		if err != nil {
			results[n] = err
		}
	}
	return results
}

func heartbeat(client Client, logger *log.Logger, lockName string, ttl time.Duration, control chan error) {
	client.Reconnect()
	defer client.Close()
	freq := time.Duration(ttl / 2)
	elapsed := time.Duration(0)
	sleeptime := 10 * time.Millisecond
	if sleeptime > freq {
		sleeptime = freq
	}

	lock := client.NewLock(lockName)
	for {
		select {
		case <-control:
			return

		default:
			if elapsed >= freq {
				start := time.Now()
				err := lock.RefreshTTL(ttl)
				if err != nil {
					logger.Printf("client %s: heartbeat -- FATAL cannot refresh lock '%s': %s",
						client.ID(), lockName, err.Error())
					select {
					case control <- err:
						return
					case <-time.After(sleeptime):
						panic(err)
					}
				}
				logger.Printf("client %s: heartbeat -- refreshed lock '%s' for %v",
					client.ID(), lockName, ttl)
				s := sleeptime - (time.Now().Sub(start))
				time.Sleep(s)
				elapsed = s
			} else {
				time.Sleep(sleeptime)
				elapsed += sleeptime
			}
		}
	}
}

// StartHeartbeat starts a goroutine in the backgroud that will refresh the
// lock The lock will be refreshed every ttl/2 or whichever is greater.  The
// background goroutine will panic() if the lock cannot be refreshed for any
// reason The background goroutine will run forever until StopHeartbeat is
// called or the lock released.  It will return a channel to signal if the lock
// cannot be refreshed during heartbeats (before panicking)
func (m *LockManager) StartHeartbeat(lockName string) (<-chan error, error) {
	info, err := m.Info(lockName)
	if err != nil {
		return nil, err
	}
	m.Logger.Printf("client %s: Starting heartbeats for lock '%s' every %v", m.client.ID(),
		lockName, info.TTL/2)
	m.hb[lockName] = make(chan error)
	go heartbeat(m.client.Clone(), m.Logger, lockName, info.TTL, m.hb[lockName])
	return m.hb[lockName], nil
}

// StopHeartbeat will stop the background gororoutine, if any, that is heartbeating the given lock
func (m *LockManager) StopHeartbeat(lockName string) {
	if c, ok := m.hb[lockName]; ok {
		m.Logger.Printf("client %s: Stopping heartbeats for lock '%s'", m.client.ID(),
			lockName)
		c <- errors.New("")
		close(c)
		delete(m.hb, lockName)
	}
}
<|endoftext|>


<commit_before>package plugins

import (
	"log"
	"os"
	"reflect"
	"runtime"
	"time"
)

var logger = log.New(os.Stderr, "[plugins::Manager] ", log.LstdFlags)

type Manager struct {
	plugins       plugins
	closers       map[string]chan<- struct{}
	subscriptions map[Event]plugins
	selects       []reflect.SelectCase
	handler       Handler
}

type plugins map[string]Plugin

// Returns a new plugin Manager ready for use.
// A default Muxer is installed as the handler
func NewManager() *Manager {
	return &Manager{
		plugins:       make(plugins),
		closers:       make(map[string]chan<- struct{}),
		subscriptions: make(map[Event]plugins),
		selects:       make([]reflect.SelectCase, 0, 16),
		handler:       make(mapMuxr),
	}
}

type packet struct {
	Event Event
	Args  Args
}

func (ph *Manager) Handle(pl Plugin) {
	ph.plugins[pl.Name()] = pl

	for _, sub := range pl.Subscribes() {
		if ph.subscriptions[sub] == nil {
			ph.subscriptions[sub] = make(plugins)
		}
		ph.subscriptions[sub][pl.Name()] = pl
	}

	f, ch, closer := generatefunc(pl)
	ph.closers[pl.Name()] = closer

	go f()

	rch := reflect.ValueOf(ch)
	ph.selects = append(ph.selects,
		reflect.SelectCase{
			Dir:  reflect.SelectRecv,
			Chan: rch,
		})
}

func (ph *Manager) HandleAll(pls ...Plugin) {
	for _, pl := range pls {
		ph.Handle(pl)
	}
}

func (ph Manager) Handler() Handler {
	return ph.handler
}

func (ph Manager) SetHandler(h Handler) {
	ph.handler = h
}

//Returns the installed handler if it is a Muxer
//Otherwise, returns nil
func (ph Manager) Muxer() Muxer {
	if muxr, ok := ph.handler.(Muxer); ok {
		return muxr
	}
	return nil
}

func generatefunc(pl Plugin) (func(), <-chan packet, chan<- struct{}) {
	ch, closer := make(chan packet, 3), make(chan struct{})
	return func() {
		defer close(ch)

		if len(pl.Provides()) == 0 {
			return //Nothing is provided, so nothing to listen for.
		}

		for {
			select {
			case <-closer:
				return
			default:
				event, args, err := pl.Recieve() //Probably blocking. Hopefully not for to long.
				if err != nil {
					switch err {
					case Unblocking:
						time.Sleep(500 * time.Millisecond)
					case NotImplemented:
						fallthrough
					default:
						logger.Printf("Encountered a error recieving event from %s: \n%s", pl.Name(), err.Error())
						return
					}
					continue
				}
				ch <- packet{event, args}
			}
			runtime.Gosched() // Allow something else to run if tight loop.
		}
	}, ch, closer
}

func (ph *Manager) Unload(pl Plugin) {
	close(ph.closers[pl.Name()])
	delete(ph.closers, pl.Name())

	for _, sub := range pl.Subscribes() {
		delete(ph.subscriptions[sub], pl.Name())
	}

	delete(ph.plugins, pl.Name())
}

func (ph *Manager) ListenAndServe() error {
	for {
		if len(ph.selects) <= 0 {
			runtime.Gosched()
			continue
		}
		if chosen, recv, ok := reflect.Select(ph.selects); ok {
			pck := recv.Interface().(packet)
			ph.dispatch("plugin", pck.Event, pck.Args)
		} else if chosen >= 0 && chosen < len(ph.selects) {
			//Not ok
			//Removing select case from list
			if chosen == len(ph.selects)-1 {
				ph.selects = ph.selects[:len(ph.selects)-1]
			} else {
				ph.selects = append(ph.selects[:chosen], ph.selects[chosen+1:]...)
			}
		}
	}

	return nil
}

func (ph Manager) Dispatch(e Event, args Args) error {
	return ph.dispatch("local", e, args)
}

func (ph Manager) dispatch(identifier string, e Event, args Args) (err error) {
	ph.Handler().HandleEvent(e, args)
	for _, plugin := range ph.subscriptions[e] {
		go func(plugin Plugin) {
			tmp := plugin.Send(e, args)
			if err == nil && tmp != NotImplemented {
				err = tmp
			}
		}(plugin)
	}
	return
}
<commit_msg>Improved documentation of manager<commit_after>package plugins

import (
	"log"
	"os"
	"reflect"
	"runtime"
	"time"
)

var logger = log.New(os.Stderr, "[plugins::Manager] ", log.LstdFlags)

//A plugin manager
//Manages plugins by routing events between them.
//Also allows a local handler to recieve the events and react to them.
type Manager struct {
	plugins       plugins
	closers       map[string]chan<- struct{}
	subscriptions map[Event]plugins
	selects       []reflect.SelectCase
	handler       Handler
}

type plugins map[string]Plugin

// Returns a new plugin Manager ready for use.
// A default Muxer is installed as the handler
func NewManager() *Manager {
	return &Manager{
		plugins:       make(plugins),
		closers:       make(map[string]chan<- struct{}),
		subscriptions: make(map[Event]plugins),
		selects:       make([]reflect.SelectCase, 0, 16),
		handler:       make(mapMuxr),
	}
}

type packet struct {
	Event Event
	Args  Args
}

//Registers a plugin to be handled by the handler.
//The name of the plugin must be unique
func (ph *Manager) Handle(pl Plugin) {
	if _, ok := ph.plugins[pl.Name()]; ok {
		//Already exists
		panic(DuplicatePlugin)
	}

	ph.plugins[pl.Name()] = pl

	for _, sub := range pl.Subscribes() {
		if ph.subscriptions[sub] == nil {
			ph.subscriptions[sub] = make(plugins)
		}
		ph.subscriptions[sub][pl.Name()] = pl
	}

	f, ch, closer := generatefunc(pl)
	ph.closers[pl.Name()] = closer

	go f()

	rch := reflect.ValueOf(ch)
	ph.selects = append(ph.selects,
		reflect.SelectCase{
			Dir:  reflect.SelectRecv,
			Chan: rch,
		})
}

//Convenience method
//Equivalent to calling Handle on all plugins individually
func (ph *Manager) HandleAll(pls ...Plugin) {
	for _, pl := range pls {
		ph.Handle(pl)
	}
}

//Returns the current handler used.
func (ph Manager) Handler() Handler {
	return ph.handler
}

//Sets the handler to be used.
func (ph *Manager) SetHandler(h Handler) {
	if h == nil {
		return
	}
	ph.handler = h
}

//Returns the installed handler if it is a Muxer
//Otherwise, returns nil
func (ph Manager) Muxer() Muxer {
	if muxr, ok := ph.handler.(Muxer); ok {
		return muxr
	}
	return nil
}

func generatefunc(pl Plugin) (func(), <-chan packet, chan<- struct{}) {
	ch, closer := make(chan packet, 3), make(chan struct{})
	return func() {
		defer close(ch)

		if len(pl.Provides()) == 0 {
			return //Nothing is provided, so nothing to listen for.
		}

		for {
			select {
			case <-closer:
				return
			default:
				event, args, err := pl.Recieve() //Probably blocking. Hopefully not for to long.
				if err != nil {
					switch err {
					case Unblocking:
						time.Sleep(500 * time.Millisecond)
					case NotImplemented:
						fallthrough
					default:
						logger.Printf("Encountered a error recieving event from %s: \n%s", pl.Name(), err.Error())
						return
					}
					continue
				}
				ch <- packet{event, args}
			}
			runtime.Gosched() // Allow something else to run if tight loop.
		}
	}, ch, closer
}

//Unhandles the plugin.
//The plugin will not recieve future events.
func (ph *Manager) Unhandle(pl Plugin) {
	close(ph.closers[pl.Name()])
	delete(ph.closers, pl.Name())

	for _, sub := range pl.Subscribes() {
		delete(ph.subscriptions[sub], pl.Name())
	}

	delete(ph.plugins, pl.Name())
}

func (ph *Manager) ListenAndServe() error {
	for {
		if len(ph.selects) <= 0 {
			runtime.Gosched()
			continue
		}
		if chosen, recv, ok := reflect.Select(ph.selects); ok {
			pck := recv.Interface().(packet)
			ph.dispatch("plugin", pck.Event, pck.Args)
		} else if chosen >= 0 && chosen < len(ph.selects) {
			//Not ok
			//Removing select case from list
			if chosen == len(ph.selects)-1 {
				ph.selects = ph.selects[:len(ph.selects)-1]
			} else {
				ph.selects = append(ph.selects[:chosen], ph.selects[chosen+1:]...)
			}
		}
	}
}

//Introduces a new event into the handler for processing
//and sending to relevant plugins.
func (ph Manager) Dispatch(e Event, args Args) error {
	return ph.dispatch("local", e, args)
}

func (ph Manager) dispatch(identifier string, e Event, args Args) (err error) {
	ph.Handler().HandleEvent(e, args)
	for _, plugin := range ph.subscriptions[e] {
		go func(plugin Plugin) {
			tmp := plugin.Send(e, args)
			if err == nil && tmp != NotImplemented {
				err = tmp
			}
		}(plugin)
	}
	return
}
<|endoftext|>


<commit_before>package gochimp3

import (
	"errors"
	"fmt"
)

const (
	members_path       = "/lists/%s/members"
	single_member_path = members_path + "/%s"

	member_activity_path = single_member_path + "/activity"
	member_goals_path    = single_member_path + "/goals"

	member_notes_path       = single_member_path + "/notes"
	single_member_note_path = member_notes_path + "/%s"
)

type ListOfMembers struct {
	baseList

	ListID  string   `json:"list_id"`
	Members []Member `json:"members"`
}

type MemberRequest struct {
	EmailAddress    string                 `json:"email_address"`
	EmailType       string                 `json:"email_type,omitempty"`
	Status          string                 `json:"status"`
	StatusIfNew     string                 `json:"status_if_new,omitempty"`
	MergeFields     map[string]interface{} `json:"merge_fields,omitempty"`
	Interests       map[string]bool        `json:"interests,omitempty"`
	Language        string                 `json:"language"`
	VIP             bool                   `json:"vip"`
	Location        *MemberLocation        `json:"location,omitempty"`
	IPOpt           string                 `json:"ip_opt,omitempty"`
	IPSignup        string                 `json:"ip_signup,omitempty"`
  Tags            []string               `json:"tags,omitempty"`
	TimestampSignup string                 `json:"timestamp_signup,omitempty"`
	TimestampOpt    string                 `json:"timestamp_opt,omitempty"`
}

type Member struct {
	MemberRequest

	ID            string          `json:"id"`
	ListID        string          `json:"list_id"`
	UniqueEmailID string          `json:"unique_email_id"`
	EmailType     string          `json:"email_type"`
	Stats         MemberStats     `json:"stats"`
	MemberRating  int             `json:"member_rating"`
	LastChanged   string          `json:"last_changed"`
	EmailClient   string          `json:"email_client"`
	LastNote      MemberNoteShort `json:"last_note"`

	api *API
}

func (mem Member) CanMakeRequest() error {
	if mem.ListID == "" {
		return errors.New("No ListID provided")
	}

	if mem.ID == "" {
		return errors.New("No ID provided")
	}

	return nil
}

type MemberStats struct {
	AvgOpenRate  float64 `json:"avg_open_rate"`
	AvgClickRate float64 `json:"avg_click_rate"`
}

type MemberLocation struct {
	Latitude    float64 `json:"latitude"`
	Longitude   float64 `json:"longitude"`
	GMTOffset   int     `json:"gmtoff"`
	DSTOffset   int     `json:"dstoff"`
	CountryCode string  `json:"country_code"`
	Timezone    string  `json:"timezone"`
}

type MemberNoteShort struct {
	ID        int    `json:"note_id"`
	CreatedAt string `json:"created_at"`
	CreatedBy string `json:"created_by"`
	Note      string `json:"note"`
}

type MemberTag struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
}

func (list ListResponse) GetMembers(params *InterestCategoriesQueryParams) (*ListOfMembers, error) {
	if err := list.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(members_path, list.ID)
	response := new(ListOfMembers)

	err := list.api.Request("GET", endpoint, params, nil, response)
	if err != nil {
		return nil, err
	}

	for _, m := range response.Members {
		m.api = list.api
	}

	return response, nil
}

func (list ListResponse) GetMember(id string, params *BasicQueryParams) (*Member, error) {
	if err := list.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(single_member_path, list.ID, id)
	response := new(Member)
	response.api = list.api

	return response, list.api.Request("GET", endpoint, params, nil, response)
}

func (list ListResponse) CreateMember(body *MemberRequest) (*Member, error) {
	if err := list.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(members_path, list.ID)
	response := new(Member)
	response.api = list.api

	return response, list.api.Request("POST", endpoint, nil, body, response)
}

func (list ListResponse) UpdateMember(id string, body *MemberRequest) (*Member, error) {
	if err := list.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(single_member_path, list.ID, id)
	response := new(Member)
	response.api = list.api

	return response, list.api.Request("PATCH", endpoint, nil, body, response)
}

func (list ListResponse) AddOrUpdateMember(id string, body *MemberRequest) (*Member, error) {
	if err := list.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(single_member_path, list.ID, id)
	response := new(Member)
	response.api = list.api

	return response, list.api.Request("PUT", endpoint, nil, body, response)
}

func (list ListResponse) DeleteMember(id string) (bool, error) {
	if err := list.CanMakeRequest(); err != nil {
		return false, err
	}

	endpoint := fmt.Sprintf(single_member_path, list.ID, id)
	return list.api.RequestOk("DELETE", endpoint)
}

// ------------------------------------------------------------------------------------------------
// Activity
// ------------------------------------------------------------------------------------------------

type ListOfMemberActivity struct {
	baseList

	EmailID  string     `json:"email_id"`
	ListID   string     `json:"list_id"`
	Activity []Activity `json:"activity"`
}

type MemberActivity struct {
	Action         string `json:"action"`
	Timestamp      string `json:"timestamp"`
	URL            string `json:"url"`
	Type           string `json:"type"`
	CampaignID     string `json:"campaign_id"`
	Title          string `json:"title"`
	ParentCampaign string `json:"parent_campaign"`
}

func (mem Member) GetActivity(params *BasicQueryParams) (*ListOfMemberActivity, error) {
	if err := mem.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(member_activity_path, mem.ListID, mem.ID)
	response := new(ListOfMemberActivity)

	return response, mem.api.Request("GET", endpoint, params, nil, response)
}

// ------------------------------------------------------------------------------------------------
// Goals
// ------------------------------------------------------------------------------------------------

type ListOfMemberGoals struct {
	baseList

	EmailID string       `json:"email_id"`
	ListID  string       `json:"list_id"`
	Goals   []MemberGoal `json:"goals"`
}

type MemberGoal struct {
	ID            int    `json:"goal_id"`
	Event         string `json:"event"`
	LastVisitedAt string `json:"last_visited_at"`
	Data          string `json:"data"`
}

func (mem Member) GetGoals(params *BasicQueryParams) (*ListOfMemberGoals, error) {
	if err := mem.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(member_goals_path, mem.ListID, mem.ID)
	response := new(ListOfMemberGoals)

	return response, mem.api.Request("GET", endpoint, params, nil, response)
}

// ------------------------------------------------------------------------------------------------
// NOTES
// ------------------------------------------------------------------------------------------------

type ListOfMemberNotes struct {
	baseList

	EmailID string           `json:"email_id"`
	ListID  string           `json:"list_id"`
	Notes   []MemberNoteLong `json:"notes"`
}

type MemberNoteLong struct {
	ID        int    `json:"id"`
	CreatedAt string `json:"created_at"`
	CreatedBy string `json:"created_by"`
	UpdatedAt string `json:"updated_at"`
	Note      string `json:"note"`
	ListID    string `json:"list_id"`
	EmailID   string `json:"email_id"`

	withLinks
}

func (mem Member) GetNotes(params *ExtendedQueryParams) (*ListOfMemberNotes, error) {
	if err := mem.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(member_notes_path, mem.ListID, mem.ID)
	response := new(ListOfMemberNotes)

	return response, mem.api.Request("GET", endpoint, params, nil, response)
}

func (mem Member) CreateNote(msg string) (*MemberNoteLong, error) {
	if err := mem.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(member_notes_path, mem.ListID, mem.ID)
	response := new(MemberNoteLong)

	body := struct{ Note string }{
		Note: msg,
	}

	return response, mem.api.Request("POST", endpoint, nil, &body, response)
}

func (mem Member) UpdateNote(id, msg string) (*MemberNoteLong, error) {
	if err := mem.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(single_member_note_path, mem.ListID, mem.ID, id)
	response := new(MemberNoteLong)

	body := struct{ Note string }{
		Note: msg,
	}

	return response, mem.api.Request("PATCH", endpoint, nil, &body, response)
}

func (mem Member) GetNote(id string, params *BasicQueryParams) (*MemberNoteLong, error) {
	if err := mem.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(single_member_note_path, mem.ListID, mem.ID, id)
	response := new(MemberNoteLong)

	return response, mem.api.Request("GET", endpoint, params, nil, response)
}

func (mem Member) DeleteNote(id string) (bool, error) {
	if err := mem.CanMakeRequest(); err != nil {
		return false, err
	}

	endpoint := fmt.Sprintf(single_member_note_path, mem.ListID, mem.ID, id)
	return mem.api.RequestOk("DELETE", endpoint)
}
<commit_msg>Added MarketingPermissions field to Member<commit_after>package gochimp3

import (
	"errors"
	"fmt"
)

const (
	members_path       = "/lists/%s/members"
	single_member_path = members_path + "/%s"

	member_activity_path = single_member_path + "/activity"
	member_goals_path    = single_member_path + "/goals"

	member_notes_path       = single_member_path + "/notes"
	single_member_note_path = member_notes_path + "/%s"
)

type ListOfMembers struct {
	baseList

	ListID  string   `json:"list_id"`
	Members []Member `json:"members"`
}

type MemberRequest struct {
	EmailAddress         string                 `json:"email_address"`
	EmailType            string                 `json:"email_type,omitempty"`
	Status               string                 `json:"status"`
	StatusIfNew          string                 `json:"status_if_new,omitempty"`
	MergeFields          map[string]interface{} `json:"merge_fields,omitempty"`
	Interests            map[string]bool        `json:"interests,omitempty"`
	Language             string                 `json:"language"`
	VIP                  bool                   `json:"vip"`
	Location             *MemberLocation        `json:"location,omitempty"`
	MarketingPermissions *MarketingPermissions  `json:"marketing_permissions,omitempty"`
	IPOpt                string                 `json:"ip_opt,omitempty"`
	IPSignup             string                 `json:"ip_signup,omitempty"`
	Tags                 []string               `json:"tags,omitempty"`
	TimestampSignup      string                 `json:"timestamp_signup,omitempty"`
	TimestampOpt         string                 `json:"timestamp_opt,omitempty"`
}

type Member struct {
	MemberRequest

	ID            string          `json:"id"`
	ListID        string          `json:"list_id"`
	UniqueEmailID string          `json:"unique_email_id"`
	EmailType     string          `json:"email_type"`
	Stats         MemberStats     `json:"stats"`
	MemberRating  int             `json:"member_rating"`
	LastChanged   string          `json:"last_changed"`
	EmailClient   string          `json:"email_client"`
	LastNote      MemberNoteShort `json:"last_note"`

	api *API
}

func (mem Member) CanMakeRequest() error {
	if mem.ListID == "" {
		return errors.New("No ListID provided")
	}

	if mem.ID == "" {
		return errors.New("No ID provided")
	}

	return nil
}

type MemberStats struct {
	AvgOpenRate  float64 `json:"avg_open_rate"`
	AvgClickRate float64 `json:"avg_click_rate"`
}

type MemberLocation struct {
	Latitude    float64 `json:"latitude"`
	Longitude   float64 `json:"longitude"`
	GMTOffset   int     `json:"gmtoff"`
	DSTOffset   int     `json:"dstoff"`
	CountryCode string  `json:"country_code"`
	Timezone    string  `json:"timezone"`
}

type MarketingPermissions []MarketingPermission

type MarketingPermission struct {
	MarketingPermissionID string `json:"marketing_permission_id"`
	Text                  string `json:"text"`
	Enabled               bool   `json:"enabled"`
}

type MemberNoteShort struct {
	ID        int    `json:"note_id"`
	CreatedAt string `json:"created_at"`
	CreatedBy string `json:"created_by"`
	Note      string `json:"note"`
}

type MemberTag struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
}

func (list ListResponse) GetMembers(params *InterestCategoriesQueryParams) (*ListOfMembers, error) {
	if err := list.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(members_path, list.ID)
	response := new(ListOfMembers)

	err := list.api.Request("GET", endpoint, params, nil, response)
	if err != nil {
		return nil, err
	}

	for _, m := range response.Members {
		m.api = list.api
	}

	return response, nil
}

func (list ListResponse) GetMember(id string, params *BasicQueryParams) (*Member, error) {
	if err := list.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(single_member_path, list.ID, id)
	response := new(Member)
	response.api = list.api

	return response, list.api.Request("GET", endpoint, params, nil, response)
}

func (list ListResponse) CreateMember(body *MemberRequest) (*Member, error) {
	if err := list.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(members_path, list.ID)
	response := new(Member)
	response.api = list.api

	return response, list.api.Request("POST", endpoint, nil, body, response)
}

func (list ListResponse) UpdateMember(id string, body *MemberRequest) (*Member, error) {
	if err := list.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(single_member_path, list.ID, id)
	response := new(Member)
	response.api = list.api

	return response, list.api.Request("PATCH", endpoint, nil, body, response)
}

func (list ListResponse) AddOrUpdateMember(id string, body *MemberRequest) (*Member, error) {
	if err := list.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(single_member_path, list.ID, id)
	response := new(Member)
	response.api = list.api

	return response, list.api.Request("PUT", endpoint, nil, body, response)
}

func (list ListResponse) DeleteMember(id string) (bool, error) {
	if err := list.CanMakeRequest(); err != nil {
		return false, err
	}

	endpoint := fmt.Sprintf(single_member_path, list.ID, id)
	return list.api.RequestOk("DELETE", endpoint)
}

// ------------------------------------------------------------------------------------------------
// Activity
// ------------------------------------------------------------------------------------------------

type ListOfMemberActivity struct {
	baseList

	EmailID  string     `json:"email_id"`
	ListID   string     `json:"list_id"`
	Activity []Activity `json:"activity"`
}

type MemberActivity struct {
	Action         string `json:"action"`
	Timestamp      string `json:"timestamp"`
	URL            string `json:"url"`
	Type           string `json:"type"`
	CampaignID     string `json:"campaign_id"`
	Title          string `json:"title"`
	ParentCampaign string `json:"parent_campaign"`
}

func (mem Member) GetActivity(params *BasicQueryParams) (*ListOfMemberActivity, error) {
	if err := mem.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(member_activity_path, mem.ListID, mem.ID)
	response := new(ListOfMemberActivity)

	return response, mem.api.Request("GET", endpoint, params, nil, response)
}

// ------------------------------------------------------------------------------------------------
// Goals
// ------------------------------------------------------------------------------------------------

type ListOfMemberGoals struct {
	baseList

	EmailID string       `json:"email_id"`
	ListID  string       `json:"list_id"`
	Goals   []MemberGoal `json:"goals"`
}

type MemberGoal struct {
	ID            int    `json:"goal_id"`
	Event         string `json:"event"`
	LastVisitedAt string `json:"last_visited_at"`
	Data          string `json:"data"`
}

func (mem Member) GetGoals(params *BasicQueryParams) (*ListOfMemberGoals, error) {
	if err := mem.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(member_goals_path, mem.ListID, mem.ID)
	response := new(ListOfMemberGoals)

	return response, mem.api.Request("GET", endpoint, params, nil, response)
}

// ------------------------------------------------------------------------------------------------
// NOTES
// ------------------------------------------------------------------------------------------------

type ListOfMemberNotes struct {
	baseList

	EmailID string           `json:"email_id"`
	ListID  string           `json:"list_id"`
	Notes   []MemberNoteLong `json:"notes"`
}

type MemberNoteLong struct {
	ID        int    `json:"id"`
	CreatedAt string `json:"created_at"`
	CreatedBy string `json:"created_by"`
	UpdatedAt string `json:"updated_at"`
	Note      string `json:"note"`
	ListID    string `json:"list_id"`
	EmailID   string `json:"email_id"`

	withLinks
}

func (mem Member) GetNotes(params *ExtendedQueryParams) (*ListOfMemberNotes, error) {
	if err := mem.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(member_notes_path, mem.ListID, mem.ID)
	response := new(ListOfMemberNotes)

	return response, mem.api.Request("GET", endpoint, params, nil, response)
}

func (mem Member) CreateNote(msg string) (*MemberNoteLong, error) {
	if err := mem.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(member_notes_path, mem.ListID, mem.ID)
	response := new(MemberNoteLong)

	body := struct{ Note string }{
		Note: msg,
	}

	return response, mem.api.Request("POST", endpoint, nil, &body, response)
}

func (mem Member) UpdateNote(id, msg string) (*MemberNoteLong, error) {
	if err := mem.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(single_member_note_path, mem.ListID, mem.ID, id)
	response := new(MemberNoteLong)

	body := struct{ Note string }{
		Note: msg,
	}

	return response, mem.api.Request("PATCH", endpoint, nil, &body, response)
}

func (mem Member) GetNote(id string, params *BasicQueryParams) (*MemberNoteLong, error) {
	if err := mem.CanMakeRequest(); err != nil {
		return nil, err
	}

	endpoint := fmt.Sprintf(single_member_note_path, mem.ListID, mem.ID, id)
	response := new(MemberNoteLong)

	return response, mem.api.Request("GET", endpoint, params, nil, response)
}

func (mem Member) DeleteNote(id string) (bool, error) {
	if err := mem.CanMakeRequest(); err != nil {
		return false, err
	}

	endpoint := fmt.Sprintf(single_member_note_path, mem.ListID, mem.ID, id)
	return mem.api.RequestOk("DELETE", endpoint)
}
<|endoftext|>


<commit_before>package mitake

import (
	"bufio"
	"errors"
	"io"
	"net/http"
	"regexp"
	"strconv"
	"strings"
)

// StatusCode of Mitake API.
type StatusCode string

func (c StatusCode) String() string {
	return statusCodeMap[c]
}

// List of Mitake API status codes
const (
	StatusServiceError                  = StatusCode("*")
	StatusSMSTemporarilyUnavailable     = StatusCode("a")
	StatusSMSTemporarilyUnavailableB    = StatusCode("b")
	StatusUsernameRequired              = StatusCode("c")
	StatusPasswordRequired              = StatusCode("d")
	StatusUsernameOrPasswordError       = StatusCode("e")
	StatusAccountExpired                = StatusCode("f")
	StatusAccountDisabled               = StatusCode("g")
	StatusInvalidConnectionAddress      = StatusCode("k")
	StatusChangePasswordRequired        = StatusCode("m")
	StatusPasswordExpired               = StatusCode("n")
	StatusPermissionDenied              = StatusCode("p")
	StatusServiceTemporarilyUnavailable = StatusCode("r")
	StatusAccountingFailure             = StatusCode("s")
	StatusSMSExpired                    = StatusCode("t")
	StatusSMSBodyEmpty                  = StatusCode("u")
	StatusInvalidPhoneNumber            = StatusCode("v")
	StatusReservationForDelivery        = StatusCode("0")
	StatusSMSContentError               = StatusCode("1")
	StatusSMSPhoneNumberError           = StatusCode("2")
	StatusCarrierAccepted               = StatusCode("3")
	StatusDelivered                     = StatusCode("4")
	StatusContentError                  = StatusCode("5")
	StatusPhoneNumberError              = StatusCode("6")
	StatusSMSDisable                    = StatusCode("7")
	StatusDeliveryTimeout               = StatusCode("8")
	StatusReservationCanceled           = StatusCode("9")
)

var statusCodeMap = map[StatusCode]string{
	StatusServiceError:                  "系統發生錯誤，請聯絡三竹資訊窗口人員",
	StatusSMSTemporarilyUnavailable:     "簡訊發送功能暫時停止服務，請稍候再試",
	StatusSMSTemporarilyUnavailableB:    "簡訊發送功能暫時停止服務，請稍候再試",
	StatusUsernameRequired:              "請輸入帳號",
	StatusPasswordRequired:              "請輸入密碼",
	StatusUsernameOrPasswordError:       "帳號、密碼錯誤",
	StatusAccountExpired:                "帳號已過期",
	StatusAccountDisabled:               "帳號已被停用",
	StatusInvalidConnectionAddress:      "無效的連線位址",
	StatusChangePasswordRequired:        "必須變更密碼，在變更密碼前，無法使用簡訊發送服務",
	StatusPasswordExpired:               "密碼已逾期，在變更密碼前，將無法使用簡訊發送服務",
	StatusPermissionDenied:              "沒有權限使用外部Http程式",
	StatusServiceTemporarilyUnavailable: "系統暫停服務，請稍後再試",
	StatusAccountingFailure:             "帳務處理失敗，無法發送簡訊",
	StatusSMSExpired:                    "簡訊已過期",
	StatusSMSBodyEmpty:                  "簡訊內容不得為空白",
	StatusInvalidPhoneNumber:            "無效的手機號碼",
	StatusReservationForDelivery:        "預約傳送中",
	StatusSMSContentError:               "已送達業者",
	StatusSMSPhoneNumberError:           "已送達業者",
	StatusCarrierAccepted:               "已送達業者",
	StatusDelivered:                     "已送達手機",
	StatusContentError:                  "內容有錯誤",
	StatusPhoneNumberError:              "門號有錯誤",
	StatusSMSDisable:                    "簡訊已停用",
	StatusDeliveryTimeout:               "逾時無送達",
	StatusReservationCanceled:           "預約已取消",
}

// Message represents an SMS object.
type Message struct {
	Dstaddr string `json:"dstaddr"` // Destination phone number
	Smbody  string `json:"smbody"`  // The text of the message you want to send
	Dlvtime string `json:"dlvtime"` // Optional, Delivery time
	Vldtime string `json:"vldtime"` // Optional
}

// ToINI returns the INI format string from the message fields.
func (m Message) ToINI() string {
	var ini string
	ini += "dstaddr=" + m.Dstaddr + "\n"
	ini += "smbody=" + m.Smbody + "\n"
	if m.Dlvtime != "" {
		ini += "dlvtime=" + m.Dlvtime + "\n"
	}
	if m.Vldtime != "" {
		ini += "vldtime=" + m.Vldtime + "\n"
	}
	return ini
}

type MessageResult struct {
	Msgid      string     `json:"msgid"`
	Statuscode StatusCode `json:"statuscode"`
}

type MessageResponse struct {
	Results      []*MessageResult
	AccountPoint int
}

func parseMessageResponse(body io.Reader) (*MessageResponse, error) {
	var (
		scanner  = bufio.NewScanner(body)
		response = new(MessageResponse)
		result   *MessageResult
	)
	for scanner.Scan() {
		text := strings.TrimSpace(scanner.Text())

		if matched, _ := regexp.MatchString(`^\[\d+]$`, text); matched {
			result = new(MessageResult)
			response.Results = append(response.Results, result)
		} else {
			strs := strings.Split(text, "=")
			switch strs[0] {
			case "msgid":
				result.Msgid = strs[1]
			case "statuscode":
				result.Statuscode = StatusCode(strs[1])
			case "AccountPoint":
				response.AccountPoint, _ = strconv.Atoi(strs[1])
			}
		}
	}
	if err := scanner.Err(); err != nil {
		return nil, err
	}
	return response, nil
}

type MessageStatus struct {
	MessageResult
	StatusTime string `json:"statustime"`
}

type MessageStatusResponse struct {
	Statuses []*MessageStatus
}

func parseMessageStatusResponse(body io.Reader) (*MessageStatusResponse, error) {
	var (
		scanner  = bufio.NewScanner(body)
		response = new(MessageStatusResponse)
	)
	for scanner.Scan() {
		text := strings.TrimSpace(scanner.Text())

		strs := strings.Split(text, "\t")
		response.Statuses = append(response.Statuses, &MessageStatus{
			MessageResult: MessageResult{
				Msgid:      strs[0],
				Statuscode: StatusCode(strs[1]),
			},
			StatusTime: strs[2],
		})
	}
	if err := scanner.Err(); err != nil {
		return nil, err
	}
	return response, nil
}

// MessageReceipt represents a message delivery receipt.
type MessageReceipt struct {
	Msgid      string     `json:"msgid"`
	Dstaddr    string     `json:"dstaddr"`
	Dlvtime    string     `json:"dlvtime"`
	Donetime   string     `json:"donetime"`
	Statuscode StatusCode `json:"statuscode"`
	Statusstr  string     `json:"statusstr"`
	StatusFlag string     `json:"StatusFlag"`
}

// ParseMessageReceipt parse an incoming Mitake callback request and return the MessageReceipt.
//
// Example usage:
//
// 	func Callback(w http.ResponseWriter, r *http.Request) {
// 		receipt, err := mitake.ParseMessageReceipt(r)
// 		if err != nil { ... }
//		// Process message receipt
// 	}
//
func ParseMessageReceipt(r *http.Request) (*MessageReceipt, error) {
	values := r.URL.Query()
	if values.Get("msgid") == "" {
		return nil, errors.New("receipt not found")
	}
	return &MessageReceipt{
		Msgid:      values.Get("msgid"),
		Dstaddr:    values.Get("dstaddr"),
		Dlvtime:    values.Get("dlvtime"),
		Donetime:   values.Get("donetime"),
		Statuscode: StatusCode(values.Get("statuscode")),
		Statusstr:  values.Get("statusstr"),
		StatusFlag: values.Get("StatusFlag"),
	}, nil
}
<commit_msg>Fix status code h<commit_after>package mitake

import (
	"bufio"
	"errors"
	"io"
	"net/http"
	"regexp"
	"strconv"
	"strings"
)

// StatusCode of Mitake API.
type StatusCode string

func (c StatusCode) String() string {
	return statusCodeMap[c]
}

// List of Mitake API status codes
const (
	StatusServiceError                  = StatusCode("*")
	StatusSMSTemporarilyUnavailable     = StatusCode("a")
	StatusSMSTemporarilyUnavailableB    = StatusCode("b")
	StatusUsernameRequired              = StatusCode("c")
	StatusPasswordRequired              = StatusCode("d")
	StatusUsernameOrPasswordError       = StatusCode("e")
	StatusAccountExpired                = StatusCode("f")
	StatusAccountDisabled               = StatusCode("h")
	StatusInvalidConnectionAddress      = StatusCode("k")
	StatusChangePasswordRequired        = StatusCode("m")
	StatusPasswordExpired               = StatusCode("n")
	StatusPermissionDenied              = StatusCode("p")
	StatusServiceTemporarilyUnavailable = StatusCode("r")
	StatusAccountingFailure             = StatusCode("s")
	StatusSMSExpired                    = StatusCode("t")
	StatusSMSBodyEmpty                  = StatusCode("u")
	StatusInvalidPhoneNumber            = StatusCode("v")
	StatusReservationForDelivery        = StatusCode("0")
	StatusSMSContentError               = StatusCode("1")
	StatusSMSPhoneNumberError           = StatusCode("2")
	StatusCarrierAccepted               = StatusCode("3")
	StatusDelivered                     = StatusCode("4")
	StatusContentError                  = StatusCode("5")
	StatusPhoneNumberError              = StatusCode("6")
	StatusSMSDisable                    = StatusCode("7")
	StatusDeliveryTimeout               = StatusCode("8")
	StatusReservationCanceled           = StatusCode("9")
)

var statusCodeMap = map[StatusCode]string{
	StatusServiceError:                  "系統發生錯誤，請聯絡三竹資訊窗口人員",
	StatusSMSTemporarilyUnavailable:     "簡訊發送功能暫時停止服務，請稍候再試",
	StatusSMSTemporarilyUnavailableB:    "簡訊發送功能暫時停止服務，請稍候再試",
	StatusUsernameRequired:              "請輸入帳號",
	StatusPasswordRequired:              "請輸入密碼",
	StatusUsernameOrPasswordError:       "帳號、密碼錯誤",
	StatusAccountExpired:                "帳號已過期",
	StatusAccountDisabled:               "帳號已被停用",
	StatusInvalidConnectionAddress:      "無效的連線位址",
	StatusChangePasswordRequired:        "必須變更密碼，在變更密碼前，無法使用簡訊發送服務",
	StatusPasswordExpired:               "密碼已逾期，在變更密碼前，將無法使用簡訊發送服務",
	StatusPermissionDenied:              "沒有權限使用外部Http程式",
	StatusServiceTemporarilyUnavailable: "系統暫停服務，請稍後再試",
	StatusAccountingFailure:             "帳務處理失敗，無法發送簡訊",
	StatusSMSExpired:                    "簡訊已過期",
	StatusSMSBodyEmpty:                  "簡訊內容不得為空白",
	StatusInvalidPhoneNumber:            "無效的手機號碼",
	StatusReservationForDelivery:        "預約傳送中",
	StatusSMSContentError:               "已送達業者",
	StatusSMSPhoneNumberError:           "已送達業者",
	StatusCarrierAccepted:               "已送達業者",
	StatusDelivered:                     "已送達手機",
	StatusContentError:                  "內容有錯誤",
	StatusPhoneNumberError:              "門號有錯誤",
	StatusSMSDisable:                    "簡訊已停用",
	StatusDeliveryTimeout:               "逾時無送達",
	StatusReservationCanceled:           "預約已取消",
}

// Message represents an SMS object.
type Message struct {
	Dstaddr string `json:"dstaddr"` // Destination phone number
	Smbody  string `json:"smbody"`  // The text of the message you want to send
	Dlvtime string `json:"dlvtime"` // Optional, Delivery time
	Vldtime string `json:"vldtime"` // Optional
}

// ToINI returns the INI format string from the message fields.
func (m Message) ToINI() string {
	var ini string
	ini += "dstaddr=" + m.Dstaddr + "\n"
	ini += "smbody=" + m.Smbody + "\n"
	if m.Dlvtime != "" {
		ini += "dlvtime=" + m.Dlvtime + "\n"
	}
	if m.Vldtime != "" {
		ini += "vldtime=" + m.Vldtime + "\n"
	}
	return ini
}

type MessageResult struct {
	Msgid      string     `json:"msgid"`
	Statuscode StatusCode `json:"statuscode"`
}

type MessageResponse struct {
	Results      []*MessageResult
	AccountPoint int
}

func parseMessageResponse(body io.Reader) (*MessageResponse, error) {
	var (
		scanner  = bufio.NewScanner(body)
		response = new(MessageResponse)
		result   *MessageResult
	)
	for scanner.Scan() {
		text := strings.TrimSpace(scanner.Text())

		if matched, _ := regexp.MatchString(`^\[\d+]$`, text); matched {
			result = new(MessageResult)
			response.Results = append(response.Results, result)
		} else {
			strs := strings.Split(text, "=")
			switch strs[0] {
			case "msgid":
				result.Msgid = strs[1]
			case "statuscode":
				result.Statuscode = StatusCode(strs[1])
			case "AccountPoint":
				response.AccountPoint, _ = strconv.Atoi(strs[1])
			}
		}
	}
	if err := scanner.Err(); err != nil {
		return nil, err
	}
	return response, nil
}

type MessageStatus struct {
	MessageResult
	StatusTime string `json:"statustime"`
}

type MessageStatusResponse struct {
	Statuses []*MessageStatus
}

func parseMessageStatusResponse(body io.Reader) (*MessageStatusResponse, error) {
	var (
		scanner  = bufio.NewScanner(body)
		response = new(MessageStatusResponse)
	)
	for scanner.Scan() {
		text := strings.TrimSpace(scanner.Text())

		strs := strings.Split(text, "\t")
		response.Statuses = append(response.Statuses, &MessageStatus{
			MessageResult: MessageResult{
				Msgid:      strs[0],
				Statuscode: StatusCode(strs[1]),
			},
			StatusTime: strs[2],
		})
	}
	if err := scanner.Err(); err != nil {
		return nil, err
	}
	return response, nil
}

// MessageReceipt represents a message delivery receipt.
type MessageReceipt struct {
	Msgid      string     `json:"msgid"`
	Dstaddr    string     `json:"dstaddr"`
	Dlvtime    string     `json:"dlvtime"`
	Donetime   string     `json:"donetime"`
	Statuscode StatusCode `json:"statuscode"`
	Statusstr  string     `json:"statusstr"`
	StatusFlag string     `json:"StatusFlag"`
}

// ParseMessageReceipt parse an incoming Mitake callback request and return the MessageReceipt.
//
// Example usage:
//
// 	func Callback(w http.ResponseWriter, r *http.Request) {
// 		receipt, err := mitake.ParseMessageReceipt(r)
// 		if err != nil { ... }
//		// Process message receipt
// 	}
//
func ParseMessageReceipt(r *http.Request) (*MessageReceipt, error) {
	values := r.URL.Query()
	if values.Get("msgid") == "" {
		return nil, errors.New("receipt not found")
	}
	return &MessageReceipt{
		Msgid:      values.Get("msgid"),
		Dstaddr:    values.Get("dstaddr"),
		Dlvtime:    values.Get("dlvtime"),
		Donetime:   values.Get("donetime"),
		Statuscode: StatusCode(values.Get("statuscode")),
		Statusstr:  values.Get("statusstr"),
		StatusFlag: values.Get("StatusFlag"),
	}, nil
}
<|endoftext|>