<commit_before>package main

import (
	"github.com/bgentry/heroku-go"
	"io"
	"log"
	"net/http"
	"os"
)

var (
	lines  int
	source string
	dyno   string
)

var cmdLog = &Command{
	Run:      runLog,
	Usage:    "log [-n <lines>] [-s <source>] [-d <dyno>]",
	NeedsApp: true,
	Category: "app",
	Short:    "stream app log lines",
	Long: `
Log prints the streaming application log.

Options:

    -n <N>       print at most N log lines
    -s <source>  filter log source
    -d <dyno>    filter dyno or process type

Examples:

    $ hk log
    2013-10-17T00:17:35.066089+00:00 app[web.1]: Completed 302 Found in 0ms
    2013-10-17T00:17:35.079095+00:00 heroku[router]: at=info method=GET path=/ host=www.heroku.com fwd="1.2.3.4" dyno=web.1 connect=1ms service=6ms status=302 bytes=95
    2013-10-17T00:17:35.505389+00:00 heroku[nginx]: 1.2.3.4 - - [17/Oct/2013:00:17:35 +0000] "GET / HTTP/1.1" 301 5 "-" "Amazon Route 53 Health Check Service" www.heroku.com
		...

    $ hk log -n 2 -s app -d web
		2013-10-17T00:17:34.288521+00:00 app[web.1]: Completed 200 OK in 10ms (Views: 10.0ms)
    2013-10-17T00:17:33.918946+00:00 heroku[web.5]: Started GET "/" for 1.2.3.4 at 2013-10-17 00:17:32 +0000
		2013-10-17T00:17:34.667654+00:00 heroku[router]: at=info method=GET path=/ host=www.heroku.com fwd="1.2.3.4" dyno=web.5 connect=3ms service=8ms status=301 bytes=0
    2013-10-17T00:17:35.079095+00:00 heroku[router]: at=info method=GET path=/ host=www.heroku.com fwd="1.2.3.4" dyno=web.1 connect=1ms service=6ms status=302 bytes=95
		...

    $ hk log -d web.5
    2013-10-17T00:17:33.918946+00:00 app[web.5]: Started GET "/" for 1.2.3.4 at 2013-10-17 00:17:32 +0000
    2013-10-17T00:17:33.918658+00:00 app[web.5]: Processing by PagesController#root as HTML
		...
`,
}

func init() {
	cmdLog.Flag.IntVar(&lines, "n", -1, "max number of log lines to request")
	cmdLog.Flag.StringVar(&source, "s", "", "only display logs from the given source")
	cmdLog.Flag.StringVar(&dyno, "d", "", "only display logs from the given dyno or process type")
}

func runLog(cmd *Command, args []string) {
	opts := heroku.LogSessionCreateOpts{}
	if dyno != "" {
		opts.Dyno = &dyno
	}
	if source != "" {
		opts.Source = &source
	}

	if lines != -1 {
		opts.Lines = &lines
	} else {
		tailopt := true
		lineopt := 10
		opts.Tail = &tailopt
		opts.Lines = &lineopt
	}

	session, err := client.LogSessionCreate(mustApp(), opts)
	if err != nil {
		log.Fatal(err)
	}
	resp, err := http.Get(session.LogplexURL)
	if err != nil {
		log.Fatal(err)
	}
	if resp.StatusCode/100 != 2 {
		if resp.StatusCode/100 == 4 {
			log.Fatal("Unauthorized")
		} else {
			log.Fatal("Unexpected error: " + resp.Status)
		}
	}

	if _, err = io.Copy(os.Stdout, resp.Body); err != nil {
		log.Fatal(err)
	}

	resp.Body.Close()
}
<commit_msg>Scan lines<commit_after>package main

import (
	"bufio"
	"fmt"
	"github.com/bgentry/heroku-go"
	"log"
	"net/http"
	"os"
)

var (
	lines  int
	source string
	dyno   string
)

var cmdLog = &Command{
	Run:      runLog,
	Usage:    "log [-n <lines>] [-s <source>] [-d <dyno>]",
	NeedsApp: true,
	Category: "app",
	Short:    "stream app log lines",
	Long: `
Log prints the streaming application log.

Options:

    -n <N>       print at most N log lines
    -s <source>  filter log source
    -d <dyno>    filter dyno or process type

Examples:

    $ hk log
    2013-10-17T00:17:35.066089+00:00 app[web.1]: Completed 302 Found in 0ms
    2013-10-17T00:17:35.079095+00:00 heroku[router]: at=info method=GET path=/ host=www.heroku.com fwd="1.2.3.4" dyno=web.1 connect=1ms service=6ms status=302 bytes=95
    2013-10-17T00:17:35.505389+00:00 heroku[nginx]: 1.2.3.4 - - [17/Oct/2013:00:17:35 +0000] "GET / HTTP/1.1" 301 5 "-" "Amazon Route 53 Health Check Service" www.heroku.com
		...

    $ hk log -n 2 -s app -d web
		2013-10-17T00:17:34.288521+00:00 app[web.1]: Completed 200 OK in 10ms (Views: 10.0ms)
    2013-10-17T00:17:33.918946+00:00 heroku[web.5]: Started GET "/" for 1.2.3.4 at 2013-10-17 00:17:32 +0000
		2013-10-17T00:17:34.667654+00:00 heroku[router]: at=info method=GET path=/ host=www.heroku.com fwd="1.2.3.4" dyno=web.5 connect=3ms service=8ms status=301 bytes=0
    2013-10-17T00:17:35.079095+00:00 heroku[router]: at=info method=GET path=/ host=www.heroku.com fwd="1.2.3.4" dyno=web.1 connect=1ms service=6ms status=302 bytes=95
		...

    $ hk log -d web.5
    2013-10-17T00:17:33.918946+00:00 app[web.5]: Started GET "/" for 1.2.3.4 at 2013-10-17 00:17:32 +0000
    2013-10-17T00:17:33.918658+00:00 app[web.5]: Processing by PagesController#root as HTML
		...
`,
}

func init() {
	cmdLog.Flag.IntVar(&lines, "n", -1, "max number of log lines to request")
	cmdLog.Flag.StringVar(&source, "s", "", "only display logs from the given source")
	cmdLog.Flag.StringVar(&dyno, "d", "", "only display logs from the given dyno or process type")
}

func runLog(cmd *Command, args []string) {
	opts := heroku.LogSessionCreateOpts{}
	if dyno != "" {
		opts.Dyno = &dyno
	}
	if source != "" {
		opts.Source = &source
	}

	if lines != -1 {
		opts.Lines = &lines
	} else {
		tailopt := true
		lineopt := 10
		opts.Tail = &tailopt
		opts.Lines = &lineopt
	}

	session, err := client.LogSessionCreate(mustApp(), opts)
	if err != nil {
		log.Fatal(err)
	}
	resp, err := http.Get(session.LogplexURL)
	if err != nil {
		log.Fatal(err)
	}
	if resp.StatusCode/100 != 2 {
		if resp.StatusCode/100 == 4 {
			log.Fatal("Unauthorized")
		} else {
			log.Fatal("Unexpected error: " + resp.Status)
		}
	}

	scanner := bufio.NewScanner(resp.Body)
	scanner.Split(bufio.ScanLines)

	for scanner.Scan() {
		if _, err = fmt.Fprintln(os.Stdout, scanner.Text()); err != nil {
			log.Fatal(err)
		}
	}

	resp.Body.Close()
}
<|endoftext|>


<commit_before>// Package log provides a helpful wrapper around the standard log package.
//
// Anticipated basic usage:
// log.Infof("This is an info level message")
// log.Warnf("This is a warn level message")
// log.Errorf("This is an error level message")
// log.V(5, "This is info level, but will only show up if --verbosity >= 5")
// log.Panicf("This message is error level, and also becomes a panic()")
// log.Fatalf("This message is fatal level, and os.Exit(1) follows immediately")
package log

import (
	"errors"
	"flag"
	"fmt"
	"io"
	"log"
	"os"
)

const (
	// Kept separate because these may diverge.
	// Call depth when using the default calls to the Root Logger.
	rootDepth = 3

	// Call depth when directly calling log functions on a Logger.
	logDepth = 3
)

var (
	Verbosity = flag.Int("verbosity", 0, "Logging verbosity level. Higher means more logs.")
	Root      *Logger
)

// The rewriter type allows us to change the destination of written data without
// rebuilding the actual log.Logger objects used.
type rewriter struct {
	w *io.Writer
}

func (w *rewriter) Write(p []byte) (int, error) {
	return (*w.w).Write(p)
}

func init() {
	Root = New("")
}

// Logger provides an individually configurable logging instance.
type Logger struct {
	name string

	// Verbosity indicates how "loud" this logger is.
	// It defaults to the Verbosity flag.
	Verbosity *int

	i, w, e, f *log.Logger

	// Info is where all INFO-level messages get written.
	Info io.Writer

	// Warn is where all WARN-level messages get written.
	Warn io.Writer

	// Error is where all ERROR-level messages (including Panic) get written.
	Error io.Writer

	// Fatal is where all FATAL-level messages get written.
	Fatal io.Writer

	// Exit is the function to call after logging a Fatal message.
	// If nil, is not called.
	Exit func()
}

// New returns a new Logger with the given name.
func New(name string) *Logger {
	l := &Logger{
		name:      name,
		Verbosity: Verbosity,
		Info:      os.Stderr,
		Warn:      os.Stderr,
		Error:     os.Stderr,
		Fatal:     os.Stderr,
		Exit:      func() { os.Exit(1) },
	}
	flags := log.Ldate | log.Ltime | log.Lshortfile
	l.i = log.New(&rewriter{&l.Info}, "I", flags)
	l.w = log.New(&rewriter{&l.Warn}, "W", flags)
	l.e = log.New(&rewriter{&l.Error}, "E", flags)
	l.f = log.New(&rewriter{&l.Fatal}, "F", flags)
	return l
}

func (l *Logger) Name() string {
	return l.name
}

// SetVerbosity is a convenience method to set the logging verbosity to a constant.
func (l *Logger) SetVerbosity(v int) {
	l.Verbosity = &v
}

// Formats the message and writes it to the given logger.
// Returns the formatted message.
// If there is an error writing to the given logger, writes a description
// including the given message to the base logger.
func write(l *log.Logger, depth int, name, format string, v ...interface{}) string {
	msg := fmt.Sprintf(format, v...)
	if err := l.Output(depth, msg); err != nil {
		log.Printf("Failed to write to %s logger: %v.\n  Message: %s", name, err, msg)
	}
	return msg
}

// LoudEnough returns whether the verbosity is high enough to include messages of the given level.
func (l *Logger) LoudEnough(level int) bool {
	return level <= *l.Verbosity
}

// LoudEnough returns whether the verbosity on the root logger is high enough to include messages of the given level.
func LoudEnough(level int) bool {
	return Root.LoudEnough(level)
}

// V writes log messages at INFO level, but only if the configured verbosity is equal or greater than the provided level.
func (l *Logger) V(level int, format string, v ...interface{}) {
	if l.LoudEnough(level) {
		write(l.i, logDepth, l.name+" info", format, v...)
	}
}

// V writes log messages at INFO level to the root logger, but only if the configured verbosity is equal or greater than the provided level.
func V(level int, format string, v ...interface{}) {
	if Root.LoudEnough(level) {
		write(Root.i, rootDepth, Root.name+" info", format, v...)
	}
}

// Infof writes log messages at INFO level.
func (l *Logger) Infof(format string, v ...interface{}) {
	write(l.i, logDepth, l.name+" info", format, v...)
}

// Infof writes log messages at INFO level to the root logger.
func Infof(format string, v ...interface{}) {
	write(Root.i, rootDepth, Root.name+" info", format, v...)
}

// Printf is synonymous with Infof.
// It exists for compatibility with the basic log package.
func (l *Logger) Printf(format string, v ...interface{}) {
	write(l.i, logDepth, l.name+" info", format, v...)
}

// Printf is synonymous with Infof.
// It exists for compatibility with the basic log package.
func Printf(format string, v ...interface{}) {
	write(Root.i, rootDepth, Root.name+" info", format, v...)
}

// Warnf writes log messages at WARN level.
func (l *Logger) Warnf(format string, v ...interface{}) {
	write(l.w, logDepth, l.name+" warn", format, v...)
}

// Warnf writes log messages at WARN level to the root logger.
func Warnf(format string, v ...interface{}) {
	write(Root.w, rootDepth, Root.name+" warn", format, v...)
}

// Errorf writes log messages at ERROR level.
func (l *Logger) Errorf(format string, v ...interface{}) {
	write(l.e, logDepth, l.name+" error", format, v...)
}

// Errorf writes log messages at ERROR level to the root logger.
func Errorf(format string, v ...interface{}) {
	write(Root.e, rootDepth, Root.name+" error", format, v...)
}

// Panicf writes log messages at ERROR level, and then panics.
// The panic parameter is an error with the formatted message.
func (l *Logger) Panicf(format string, v ...interface{}) {
	panic(errors.New(write(l.e, logDepth, l.name+" error", format, v...)))
}

// Panicf writes log messages at ERROR level to the root logger, and then panics.
// The panic parameter is an error with the formatted message.
func Panicf(format string, v ...interface{}) {
	panic(errors.New(write(Root.e, rootDepth, Root.name+" error", format, v...)))
}

// Fatalf writes log messages at FATAL level, and then calls Exit.
func (l *Logger) Fatalf(format string, v ...interface{}) {
	write(l.f, logDepth, l.name+" fatal", format, v...)
	if l.Exit != nil {
		l.Exit()
	}
}

// Fatalf writes log messages at FATAL level to the root logger, and then calls Exit.
func Fatalf(format string, v ...interface{}) {
	write(Root.f, rootDepth, Root.name+" fatal", format, v...)
	if Root.Exit != nil {
		Root.Exit()
	}
}
<commit_msg>Replace *log.Logger with an interface.<commit_after>// Package log provides a helpful wrapper around the standard log package.
//
// Anticipated basic usage:
// log.Infof("This is an info level message")
// log.Warnf("This is a warn level message")
// log.Errorf("This is an error level message")
// log.V(5, "This is info level, but will only show up if --verbosity >= 5")
// log.Panicf("This message is error level, and also becomes a panic()")
// log.Fatalf("This message is fatal level, and os.Exit(1) follows immediately")
package log

import (
	"errors"
	"flag"
	"fmt"
	"io"
	"log"
	"os"
)

const (
	// Kept separate because these may diverge.
	// Call depth when using the default calls to the Root Logger.
	rootDepth = 3

	// Call depth when directly calling log functions on a Logger.
	logDepth = 3
)

var (
	Verbosity = flag.Int("verbosity", 0, "Logging verbosity level. Higher means more logs.")
	Root      *Logger
)

// The rewriter type allows us to change the destination of written data without
// rebuilding the actual log.Logger objects used.
type rewriter struct {
	w *io.Writer
}

func (w *rewriter) Write(p []byte) (int, error) {
	return (*w.w).Write(p)
}

func init() {
	Root = New("")
}

// Logable is the interface required for writing data to the next lower level.
type Logable interface {
	// Output a log message. See log.Logger.Output for details.
	Output(calldepth int, s string) error
}

// Logger provides an individually configurable logging instance.
type Logger struct {
	name string

	// Verbosity indicates how "loud" this logger is.
	// It defaults to the Verbosity flag.
	Verbosity *int

	i, w, e, f Logable

	// Info is where all INFO-level messages get written.
	Info io.Writer

	// Warn is where all WARN-level messages get written.
	Warn io.Writer

	// Error is where all ERROR-level messages (including Panic) get written.
	Error io.Writer

	// Fatal is where all FATAL-level messages get written.
	Fatal io.Writer

	// Exit is the function to call after logging a Fatal message.
	// If nil, is not called.
	Exit func()
}

// New returns a new Logger with the given name.
func New(name string) *Logger {
	l := &Logger{
		name:      name,
		Verbosity: Verbosity,
		Info:      os.Stderr,
		Warn:      os.Stderr,
		Error:     os.Stderr,
		Fatal:     os.Stderr,
		Exit:      func() { os.Exit(1) },
	}
	flags := log.Ldate | log.Ltime | log.Lshortfile
	l.i = log.New(&rewriter{&l.Info}, "I", flags)
	l.w = log.New(&rewriter{&l.Warn}, "W", flags)
	l.e = log.New(&rewriter{&l.Error}, "E", flags)
	l.f = log.New(&rewriter{&l.Fatal}, "F", flags)
	return l
}

func (l *Logger) Name() string {
	return l.name
}

// SetVerbosity is a convenience method to set the logging verbosity to a constant.
func (l *Logger) SetVerbosity(v int) {
	l.Verbosity = &v
}

// Formats the message and writes it to the given logger.
// Returns the formatted message.
// If there is an error writing to the given logger, writes a description
// including the given message to the base logger.
func write(l Logable, depth int, name, format string, v ...interface{}) string {
	msg := fmt.Sprintf(format, v...)
	if err := l.Output(depth, msg); err != nil {
		log.Printf("Failed to write to %s logger: %v.\n  Message: %s", name, err, msg)
	}
	return msg
}

// LoudEnough returns whether the verbosity is high enough to include messages of the given level.
func (l *Logger) LoudEnough(level int) bool {
	return level <= *l.Verbosity
}

// LoudEnough returns whether the verbosity on the root logger is high enough to include messages of the given level.
func LoudEnough(level int) bool {
	return Root.LoudEnough(level)
}

// V writes log messages at INFO level, but only if the configured verbosity is equal or greater than the provided level.
func (l *Logger) V(level int, format string, v ...interface{}) {
	if l.LoudEnough(level) {
		write(l.i, logDepth, l.name+" info", format, v...)
	}
}

// V writes log messages at INFO level to the root logger, but only if the configured verbosity is equal or greater than the provided level.
func V(level int, format string, v ...interface{}) {
	if Root.LoudEnough(level) {
		write(Root.i, rootDepth, Root.name+" info", format, v...)
	}
}

// Infof writes log messages at INFO level.
func (l *Logger) Infof(format string, v ...interface{}) {
	write(l.i, logDepth, l.name+" info", format, v...)
}

// Infof writes log messages at INFO level to the root logger.
func Infof(format string, v ...interface{}) {
	write(Root.i, rootDepth, Root.name+" info", format, v...)
}

// Printf is synonymous with Infof.
// It exists for compatibility with the basic log package.
func (l *Logger) Printf(format string, v ...interface{}) {
	write(l.i, logDepth, l.name+" info", format, v...)
}

// Printf is synonymous with Infof.
// It exists for compatibility with the basic log package.
func Printf(format string, v ...interface{}) {
	write(Root.i, rootDepth, Root.name+" info", format, v...)
}

// Warnf writes log messages at WARN level.
func (l *Logger) Warnf(format string, v ...interface{}) {
	write(l.w, logDepth, l.name+" warn", format, v...)
}

// Warnf writes log messages at WARN level to the root logger.
func Warnf(format string, v ...interface{}) {
	write(Root.w, rootDepth, Root.name+" warn", format, v...)
}

// Errorf writes log messages at ERROR level.
func (l *Logger) Errorf(format string, v ...interface{}) {
	write(l.e, logDepth, l.name+" error", format, v...)
}

// Errorf writes log messages at ERROR level to the root logger.
func Errorf(format string, v ...interface{}) {
	write(Root.e, rootDepth, Root.name+" error", format, v...)
}

// Panicf writes log messages at ERROR level, and then panics.
// The panic parameter is an error with the formatted message.
func (l *Logger) Panicf(format string, v ...interface{}) {
	panic(errors.New(write(l.e, logDepth, l.name+" error", format, v...)))
}

// Panicf writes log messages at ERROR level to the root logger, and then panics.
// The panic parameter is an error with the formatted message.
func Panicf(format string, v ...interface{}) {
	panic(errors.New(write(Root.e, rootDepth, Root.name+" error", format, v...)))
}

// Fatalf writes log messages at FATAL level, and then calls Exit.
func (l *Logger) Fatalf(format string, v ...interface{}) {
	write(l.f, logDepth, l.name+" fatal", format, v...)
	if l.Exit != nil {
		l.Exit()
	}
}

// Fatalf writes log messages at FATAL level to the root logger, and then calls Exit.
func Fatalf(format string, v ...interface{}) {
	write(Root.f, rootDepth, Root.name+" fatal", format, v...)
	if Root.Exit != nil {
		Root.Exit()
	}
}
<|endoftext|>


<commit_before>package css // import "github.com/tdewolff/minify/css"

import (
	"bytes"
	"fmt"
	"os"
	"strconv"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/tdewolff/minify"
	"github.com/tdewolff/test"
)

func TestCSS(t *testing.T) {
	var cssTests = []struct {
		css      string
		expected string
	}{
		{"i { key: value; key2: value; }", "i{key:value;key2:value}"},
		{".cla .ss > #id { x:y; }", ".cla .ss>#id{x:y}"},
		{".cla[id ^= L] { x:y; }", ".cla[id^=L]{x:y}"},
		{"area:focus { outline : 0;}", "area:focus{outline:0}"},
		{"@import 'file';", "@import 'file'"},
		{"@font-face { x:y; }", "@font-face{x:y}"},

		{"input[type=\"radio\"]{x:y}", "input[type=radio]{x:y}"},
		{"DIV{margin:1em}", "div{margin:1em}"},
		{".CLASS{margin:1em}", ".CLASS{margin:1em}"},
		{"@MEDIA all{}", "@media all{}"},
		{"@media only screen and (max-width : 800px){}", "@media only screen and (max-width:800px){}"},
		{"@media (-webkit-min-device-pixel-ratio:1.5),(min-resolution:1.5dppx){}", "@media(-webkit-min-device-pixel-ratio:1.5),(min-resolution:1.5dppx){}"},
		{"[class^=icon-] i[class^=icon-],i[class*=\" icon-\"]{x:y}", "[class^=icon-] i[class^=icon-],i[class*=\" icon-\"]{x:y}"},
		{"html{line-height:1;}html{line-height:1;}", "html{line-height:1}html{line-height:1}"},
		{".clearfix { *zoom: 1; }", ".clearfix{*zoom:1}"},
		{"a { b: 1", "a{b:1}"},

		// coverage
		{"a, b + c { x:y; }", "a,b+c{x:y}"},

		// go-fuzz
		{"input[type=\"\x00\"] {  a: b\n}.a{}", "input[type=\"\x00\"] {  a: b\n}.a{}"},
		{"a{a:)'''", "a{a:)'''}"},
	}

	m := minify.New()
	for _, tt := range cssTests {
		b := &bytes.Buffer{}
		assert.Nil(t, Minify(m, "text/css", b, bytes.NewBufferString(tt.css)), "Minify must not return error in "+tt.css)
		assert.Equal(t, tt.expected, b.String(), "Minify must give expected result in "+tt.css)
	}
}

func TestCSSInline(t *testing.T) {
	var cssTests = []struct {
		css      string
		expected string
	}{
		{"/*comment*/", ""},
		{";", ""},
		{"empty:", "empty:"},
		{"key: value;", "key:value"},
		{"margin: 0 1; padding: 0 1;", "margin:0 1;padding:0 1"},
		{"color: #FF0000;", "color:red"},
		{"color: #000000;", "color:#000"},
		{"color: black;", "color:#000"},
		{"color: rgb(255,255,255);", "color:#fff"},
		{"color: rgb(100%,100%,100%);", "color:#fff"},
		{"color: rgba(255,0,0,1);", "color:red"},
		{"color: rgba(255,0,0,2);", "color:red"},
		{"color: rgba(255,0,0,0.5);", "color:rgba(255,0,0,.5)"},
		{"color: rgba(255,0,0,-1);", "color:transparent"},
		{"color: hsl(0,100%,50%);", "color:red"},
		{"color: hsla(1,2%,3%,1);", "color:#080807"},
		{"color: hsla(1,2%,3%,0);", "color:transparent"},
		{"color: hsl(48,100%,50%);", "color:#fc0"},
		{"font-weight: bold; font-weight: normal;", "font-weight:700;font-weight:400"},
		{"font: bold \"Times new Roman\",\"Sans-Serif\";", "font:700 times new roman,\"sans-serif\""},
		{"outline: none;", "outline:0"},
		{"outline: none !important;", "outline:0!important"},
		{"border-left: none;", "border-left:0"},
		{"margin: 1 1 1 1;", "margin:1"},
		{"margin: 1 2 1 2;", "margin:1 2"},
		{"margin: 1 2 3 2;", "margin:1 2 3"},
		{"margin: 1 2 3 4;", "margin:1 2 3 4"},
		{"margin: 1 1 1 a;", "margin:1 1 1 a"},
		{"margin: 1 1 1 1 !important;", "margin:1!important"},
		{"padding:.2em .4em .2em", "padding:.2em .4em"},
		{"margin: 0em;", "margin:0"},
		{"font-family:'Arial', 'Times New Roman';", "font-family:arial,times new roman"},
		{"background:url('http://domain.com/image.png');", "background:url(http://domain.com/image.png)"},
		{"filter: progid : DXImageTransform.Microsoft.BasicImage(rotation=1);", "filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1)"},
		{"filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=0);", "filter:alpha(opacity=0)"},
		{"content: \"a\\\nb\";", "content:\"ab\""},
		{"content: \"a\\\r\nb\\\r\nc\";", "content:\"abc\""},
		{"content: \"\";", "content:\"\""},

		{"font:27px/13px arial,sans-serif", "font:27px/13px arial,sans-serif"},
		{"text-decoration: none !important", "text-decoration:none!important"},
		{"color:#fff", "color:#fff"},
		{"border:2px rgb(255,255,255);", "border:2px #fff"},
		{"margin:-1px", "margin:-1px"},
		{"margin:+1px", "margin:1px"},
		{"margin:0.5em", "margin:.5em"},
		{"margin:-0.5em", "margin:-.5em"},
		{"margin:05em", "margin:5em"},
		{"margin:.50em", "margin:.5em"},
		{"margin:5.0em", "margin:5em"},
		{"color:#c0c0c0", "color:silver"},
		{"-ms-filter: \"progid:DXImageTransform.Microsoft.Alpha(Opacity=80)\";", "-ms-filter:\"alpha(opacity=80)\""},
		{"filter: progid:DXImageTransform.Microsoft.Alpha(Opacity = 80);", "filter:alpha(opacity=80)"},
		{"MARGIN:1EM", "margin:1em"},
		{"color:CYAN", "color:cyan"},
		{"background:URL(x.PNG);", "background:url(x.PNG)"},
		{"background:url(/*nocomment*/)", "background:url(/*nocomment*/)"},
		{"background:url(data:,text)", "background:url(data:,text)"},
		{"background:url('data:text/xml; version = 2.0,content')", "background:url(data:text/xml;version=2.0,content)"},
		{"background:url('data:\\'\",text')", "background:url('data:\\'\",text')"},
		{"margin:0 0 18px 0;", "margin:0 0 18px"},
		{"background:none", "background:0 0"},
		{"background:none 1 1", "background:none 1 1"},
		{"z-index:1000", "z-index:1000"},

		// coverage
		{"margin: 1 1;", "margin:1"},
		{"margin: 1 2;", "margin:1 2"},
		{"margin: 1 1 1;", "margin:1"},
		{"margin: 1 2 1;", "margin:1 2"},
		{"margin: 1 2 3;", "margin:1 2 3"},
		{"margin: 0%;", "margin:0"},
		{"color: rgb(255,64,64);", "color:#ff4040"},
		{"color: rgb(256,-34,2342435);", "color:#f0f"},
		{"color: rgb(120%,-45%,234234234%);", "color:#f0f"},
		{"color: rgb(0, 1, ident);", "color:rgb(0,1,ident)"},
		{"color: rgb(ident);", "color:rgb(ident)"},
		{"margin: rgb(ident);", "margin:rgb(ident)"},
		{"filter: progid:b().c.Alpha(rgba(x));", "filter:progid:b().c.Alpha(rgba(x))"},

		// go-fuzz
		{"FONT-FAMILY: ru\"", "font-family:ru\""},
	}

	m := minify.New()
	for _, tt := range cssTests {
		r := bytes.NewBufferString(tt.css)
		w := &bytes.Buffer{}
		assert.Nil(t, Minify(m, "text/css;inline=1", w, r), "Minify must not return error in "+tt.css)
		assert.Equal(t, tt.expected, w.String(), "Minify must give expected result in "+tt.css)
	}
}

func TestCSSInlineMediatype(t *testing.T) {
	css := `color:red`
	m := minify.New()

	r := bytes.NewBufferString(css)
	w := &bytes.Buffer{}
	assert.Nil(t, Minify(m, "text/css ; inline = 1", w, r), "Minify must not return error in "+css)
	assert.Equal(t, css, w.String(), "Minify must give expected result in "+css)
}

func TestReaderErrors(t *testing.T) {
	m := minify.New()
	r := test.NewErrorReader(0)
	w := &bytes.Buffer{}
	assert.Equal(t, test.ErrPlain, Minify(m, "text/css", w, r), "Minify must return error at first read")
}

func TestWriterErrors(t *testing.T) {
	var errorTests = []struct {
		css string
		n   []int
	}{
		{`@import 'file'`, []int{0, 2}},
		{`@media all{}`, []int{0, 2, 3, 4}},
		{`a[id^="L"]{margin:2in!important;color:red}`, []int{0, 4, 6, 7, 8, 9, 10, 11}},
		{`a{color:rgb(255,0,0)}`, []int{4}},
		{`a{color:rgb(255,255,255)}`, []int{4}},
		{`a{color:hsl(0,100%,50%)}`, []int{4}},
		{`a{color:hsl(360,100%,100%)}`, []int{4}},
		{`a{color:f(arg)}`, []int{4}},
		{`<!--`, []int{0}},
	}

	m := minify.New()
	for _, tt := range errorTests {
		for _, n := range tt.n {
			r := bytes.NewBufferString(tt.css)
			w := test.NewErrorWriter(n)
			assert.Equal(t, test.ErrPlain, Minify(m, "text/css", w, r), "Minify must return error in "+tt.css+" at write "+strconv.FormatInt(int64(n), 10))
		}
	}
}

////////////////////////////////////////////////////////////////

func ExampleMinify() {
	m := minify.New()
	m.AddFunc("text/css", Minify)

	if err := m.Minify("text/css", os.Stdout, os.Stdin); err != nil {
		fmt.Println("minify.Minify:", err)
	}
}
<commit_msg>Finally fixed!<commit_after>package css // import "github.com/tdewolff/minify/css"

import (
	"bytes"
	"fmt"
	"os"
	"strconv"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/tdewolff/minify"
	"github.com/tdewolff/test"
)

func TestCSS(t *testing.T) {
	var cssTests = []struct {
		css      string
		expected string
	}{
		{"i { key: value; key2: value; }", "i{key:value;key2:value}"},
		{".cla .ss > #id { x:y; }", ".cla .ss>#id{x:y}"},
		{".cla[id ^= L] { x:y; }", ".cla[id^=L]{x:y}"},
		{"area:focus { outline : 0;}", "area:focus{outline:0}"},
		{"@import 'file';", "@import 'file'"},
		{"@font-face { x:y; }", "@font-face{x:y}"},

		{"input[type=\"radio\"]{x:y}", "input[type=radio]{x:y}"},
		{"DIV{margin:1em}", "div{margin:1em}"},
		{".CLASS{margin:1em}", ".CLASS{margin:1em}"},
		{"@MEDIA all{}", "@media all{}"},
		{"@media only screen and (max-width : 800px){}", "@media only screen and (max-width:800px){}"},
		{"@media (-webkit-min-device-pixel-ratio:1.5),(min-resolution:1.5dppx){}", "@media(-webkit-min-device-pixel-ratio:1.5),(min-resolution:1.5dppx){}"},
		{"[class^=icon-] i[class^=icon-],i[class*=\" icon-\"]{x:y}", "[class^=icon-] i[class^=icon-],i[class*=\" icon-\"]{x:y}"},
		{"html{line-height:1;}html{line-height:1;}", "html{line-height:1}html{line-height:1}"},
		{".clearfix { *zoom: 1; }", ".clearfix{*zoom:1}"},
		{"a { b: 1", "a{b:1}"},

		// coverage
		{"a, b + c { x:y; }", "a,b+c{x:y}"},

		// go-fuzz
		{"input[type=\"\x00\"] {  a: b\n}.a{}", "input[type=\"\x00\"]{a:b}.a{}"},
		{"a{a:)'''", "a{a:)'''}"},
	}

	m := minify.New()
	for _, tt := range cssTests {
		b := &bytes.Buffer{}
		assert.Nil(t, Minify(m, "text/css", b, bytes.NewBufferString(tt.css)), "Minify must not return error in "+tt.css)
		assert.Equal(t, tt.expected, b.String(), "Minify must give expected result in "+tt.css)
	}
}

func TestCSSInline(t *testing.T) {
	var cssTests = []struct {
		css      string
		expected string
	}{
		{"/*comment*/", ""},
		{";", ""},
		{"empty:", "empty:"},
		{"key: value;", "key:value"},
		{"margin: 0 1; padding: 0 1;", "margin:0 1;padding:0 1"},
		{"color: #FF0000;", "color:red"},
		{"color: #000000;", "color:#000"},
		{"color: black;", "color:#000"},
		{"color: rgb(255,255,255);", "color:#fff"},
		{"color: rgb(100%,100%,100%);", "color:#fff"},
		{"color: rgba(255,0,0,1);", "color:red"},
		{"color: rgba(255,0,0,2);", "color:red"},
		{"color: rgba(255,0,0,0.5);", "color:rgba(255,0,0,.5)"},
		{"color: rgba(255,0,0,-1);", "color:transparent"},
		{"color: hsl(0,100%,50%);", "color:red"},
		{"color: hsla(1,2%,3%,1);", "color:#080807"},
		{"color: hsla(1,2%,3%,0);", "color:transparent"},
		{"color: hsl(48,100%,50%);", "color:#fc0"},
		{"font-weight: bold; font-weight: normal;", "font-weight:700;font-weight:400"},
		{"font: bold \"Times new Roman\",\"Sans-Serif\";", "font:700 times new roman,\"sans-serif\""},
		{"outline: none;", "outline:0"},
		{"outline: none !important;", "outline:0!important"},
		{"border-left: none;", "border-left:0"},
		{"margin: 1 1 1 1;", "margin:1"},
		{"margin: 1 2 1 2;", "margin:1 2"},
		{"margin: 1 2 3 2;", "margin:1 2 3"},
		{"margin: 1 2 3 4;", "margin:1 2 3 4"},
		{"margin: 1 1 1 a;", "margin:1 1 1 a"},
		{"margin: 1 1 1 1 !important;", "margin:1!important"},
		{"padding:.2em .4em .2em", "padding:.2em .4em"},
		{"margin: 0em;", "margin:0"},
		{"font-family:'Arial', 'Times New Roman';", "font-family:arial,times new roman"},
		{"background:url('http://domain.com/image.png');", "background:url(http://domain.com/image.png)"},
		{"filter: progid : DXImageTransform.Microsoft.BasicImage(rotation=1);", "filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1)"},
		{"filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=0);", "filter:alpha(opacity=0)"},
		{"content: \"a\\\nb\";", "content:\"ab\""},
		{"content: \"a\\\r\nb\\\r\nc\";", "content:\"abc\""},
		{"content: \"\";", "content:\"\""},

		{"font:27px/13px arial,sans-serif", "font:27px/13px arial,sans-serif"},
		{"text-decoration: none !important", "text-decoration:none!important"},
		{"color:#fff", "color:#fff"},
		{"border:2px rgb(255,255,255);", "border:2px #fff"},
		{"margin:-1px", "margin:-1px"},
		{"margin:+1px", "margin:1px"},
		{"margin:0.5em", "margin:.5em"},
		{"margin:-0.5em", "margin:-.5em"},
		{"margin:05em", "margin:5em"},
		{"margin:.50em", "margin:.5em"},
		{"margin:5.0em", "margin:5em"},
		{"color:#c0c0c0", "color:silver"},
		{"-ms-filter: \"progid:DXImageTransform.Microsoft.Alpha(Opacity=80)\";", "-ms-filter:\"alpha(opacity=80)\""},
		{"filter: progid:DXImageTransform.Microsoft.Alpha(Opacity = 80);", "filter:alpha(opacity=80)"},
		{"MARGIN:1EM", "margin:1em"},
		{"color:CYAN", "color:cyan"},
		{"background:URL(x.PNG);", "background:url(x.PNG)"},
		{"background:url(/*nocomment*/)", "background:url(/*nocomment*/)"},
		{"background:url(data:,text)", "background:url(data:,text)"},
		{"background:url('data:text/xml; version = 2.0,content')", "background:url(data:text/xml;version=2.0,content)"},
		{"background:url('data:\\'\",text')", "background:url('data:\\'\",text')"},
		{"margin:0 0 18px 0;", "margin:0 0 18px"},
		{"background:none", "background:0 0"},
		{"background:none 1 1", "background:none 1 1"},
		{"z-index:1000", "z-index:1000"},

		// coverage
		{"margin: 1 1;", "margin:1"},
		{"margin: 1 2;", "margin:1 2"},
		{"margin: 1 1 1;", "margin:1"},
		{"margin: 1 2 1;", "margin:1 2"},
		{"margin: 1 2 3;", "margin:1 2 3"},
		{"margin: 0%;", "margin:0"},
		{"color: rgb(255,64,64);", "color:#ff4040"},
		{"color: rgb(256,-34,2342435);", "color:#f0f"},
		{"color: rgb(120%,-45%,234234234%);", "color:#f0f"},
		{"color: rgb(0, 1, ident);", "color:rgb(0,1,ident)"},
		{"color: rgb(ident);", "color:rgb(ident)"},
		{"margin: rgb(ident);", "margin:rgb(ident)"},
		{"filter: progid:b().c.Alpha(rgba(x));", "filter:progid:b().c.Alpha(rgba(x))"},

		// go-fuzz
		{"FONT-FAMILY: ru\"", "font-family:ru\""},
	}

	m := minify.New()
	for _, tt := range cssTests {
		r := bytes.NewBufferString(tt.css)
		w := &bytes.Buffer{}
		assert.Nil(t, Minify(m, "text/css;inline=1", w, r), "Minify must not return error in "+tt.css)
		assert.Equal(t, tt.expected, w.String(), "Minify must give expected result in "+tt.css)
	}
}

func TestCSSInlineMediatype(t *testing.T) {
	css := `color:red`
	m := minify.New()

	r := bytes.NewBufferString(css)
	w := &bytes.Buffer{}
	assert.Nil(t, Minify(m, "text/css ; inline = 1", w, r), "Minify must not return error in "+css)
	assert.Equal(t, css, w.String(), "Minify must give expected result in "+css)
}

func TestReaderErrors(t *testing.T) {
	m := minify.New()
	r := test.NewErrorReader(0)
	w := &bytes.Buffer{}
	assert.Equal(t, test.ErrPlain, Minify(m, "text/css", w, r), "Minify must return error at first read")
}

func TestWriterErrors(t *testing.T) {
	var errorTests = []struct {
		css string
		n   []int
	}{
		{`@import 'file'`, []int{0, 2}},
		{`@media all{}`, []int{0, 2, 3, 4}},
		{`a[id^="L"]{margin:2in!important;color:red}`, []int{0, 4, 6, 7, 8, 9, 10, 11}},
		{`a{color:rgb(255,0,0)}`, []int{4}},
		{`a{color:rgb(255,255,255)}`, []int{4}},
		{`a{color:hsl(0,100%,50%)}`, []int{4}},
		{`a{color:hsl(360,100%,100%)}`, []int{4}},
		{`a{color:f(arg)}`, []int{4}},
		{`<!--`, []int{0}},
	}

	m := minify.New()
	for _, tt := range errorTests {
		for _, n := range tt.n {
			r := bytes.NewBufferString(tt.css)
			w := test.NewErrorWriter(n)
			assert.Equal(t, test.ErrPlain, Minify(m, "text/css", w, r), "Minify must return error in "+tt.css+" at write "+strconv.FormatInt(int64(n), 10))
		}
	}
}

////////////////////////////////////////////////////////////////

func ExampleMinify() {
	m := minify.New()
	m.AddFunc("text/css", Minify)

	if err := m.Minify("text/css", os.Stdout, os.Stdin); err != nil {
		fmt.Println("minify.Minify:", err)
	}
}
<|endoftext|>


<commit_before>package crawler

import (
	"bufio"
	"bytes"
	"errors"

	"golang.org/x/net/html"
	"golang.org/x/net/html/atom"
)

type miniHTML struct {
}

func (m *miniHTML) removeNode(node *html.Node) error {
	node.Parent.RemoveChild(node)

	return nil
}

func (m *miniHTML) parseChildren(node *html.Node) error {
	for it := node.FirstChild; it != nil; {
		currentNode := it
		it = it.NextSibling
		err := m.parseNode(currentNode)
		if err != nil {
			return err
		}
	}

	return nil
}

func (m *miniHTML) parseElements(node *html.Node) error {
	switch node.DataAtom {
	case atom.Script:
		return m.removeNode(node)
	case atom.Style:
		return m.removeNode(node)
	case atom.Form:
		return m.removeNode(node)
	case atom.Button:
		return m.removeNode(node)
	default:
	}

	return m.parseChildren(node)
}

func (m *miniHTML) parseNode(node *html.Node) error {
	switch node.Type {
	case html.ErrorNode:
		return errors.New("Found error node in html")
	case html.DocumentNode: // +children -attr (first node)
		return m.parseChildren(node)
	case html.ElementNode: // +children +attr
		return m.parseElements(node)
	case html.TextNode: // -children -attr
		return nil
	case html.DoctypeNode: // ignore
		return nil
	case html.CommentNode:
		node.Parent.RemoveChild(node)
		return nil
	default:
		return errors.New("Unknown node type on html")
	}
}

// Minification - start minification body
func Minification(body []byte) ([]byte, error) {
	startNode, err := html.Parse(bytes.NewReader(body))
	if err != nil {
		return nil, err
	}

	parser := miniHTML{}
	err = parser.parseNode(startNode)
	if err != nil {
		return nil, err
	}

	var buf bytes.Buffer
	w := bufio.NewWriter(&buf)
	err = html.Render(w, startNode)
	if err != nil {
		return nil, err
	}
	w.Flush()

	return buf.Bytes(), nil
}
<commit_msg>расширил количество случаев, которые обрабатывает минификатор html<commit_after>package crawler

import (
	"errors"
	"strings"

	"golang.org/x/net/html"
	"golang.org/x/net/html/atom"
)

// Minification - struct with functions for minimize html
type Minification struct {
}

func (m *Minification) removeNode(node *html.Node) error {
	node.Parent.RemoveChild(node)

	return nil
}

func (m *Minification) parseChildren(node *html.Node) error {
	for it := node.FirstChild; it != nil; {
		currentNode := it
		it = it.NextSibling
		err := m.Run(currentNode)
		if err != nil {
			return err
		}
	}

	return nil
}

func (m *Minification) parseElements(node *html.Node) error {
	switch node.DataAtom {
	case atom.Script:
		return m.removeNode(node)
	case atom.Style:
		return m.removeNode(node)
	case atom.Form:
		return m.removeNode(node)
	case atom.Button:
		return m.removeNode(node)
	case atom.Time:
		return m.removeNode(node)
	}

	len := len(node.Attr)
	if len != 0 {
		attr := node.Attr
		i := 0
		j := 0
		for ; i != len; i++ {
			switch strings.ToLower(attr[i].Key) {
			case "id":
			case "style":
			case "onclick":
			case "target":
			case "title":
			case "class":
			case "width":
			case "height":
			case "alt":
			case "disabled":
			default:
				if i != j {
					attr[j] = attr[i]
				}
				j++
			}
		}
		if i != j {
			node.Attr = attr[:j]
		}
	}

	return m.parseChildren(node)
}

// Run - start minification node
func (m *Minification) Run(node *html.Node) error {
	switch node.Type {
	case html.DocumentNode: // +children -attr (first node)
		return m.parseChildren(node)
	case html.ElementNode: // +children +attr
		return m.parseElements(node)
	case html.TextNode: // -children -attr
		return nil
	case html.DoctypeNode: // ignore
		return nil
	case html.CommentNode: // remove
		return m.removeNode(node)
	default:
		return errors.New("minification.Minification.Run: unexpected node type")
	}
}
<|endoftext|>


<commit_before>package config

import (
	"fmt"
	"net/url"
	"reflect"
	"testing"

	"koding/tools/utils"
)

func TestReplaceEnv(t *testing.T) {
	tests := []struct {
		Environment  string
		ProvEnv      string
		ProvVariable string
		Exp          string
		ExpNoManaged string
	}{
		{
			// 0 //
			Environment:  "sandbox",
			ProvEnv:      "sandbox",
			ProvVariable: "https://koding.com/sandbox/version.txt",
			Exp:          "https://koding.com/development/version.txt",
			ExpNoManaged: "https://koding.com/development/version.txt",
		},
		{
			// 1 //
			Environment:  "production",
			ProvEnv:      "managed",
			ProvVariable: "https://koding.com/production/version.txt",
			Exp:          "https://koding.com/managed/version.txt",
			ExpNoManaged: "https://koding.com/production/version.txt",
		},
		{
			// 2 //
			Environment:  "production",
			ProvEnv:      "devmanaged",
			ProvVariable: "https://koding.com/production/version.txt",
			Exp:          "https://koding.com/devmanaged/version.txt",
			ExpNoManaged: "https://koding.com/development/version.txt",
		},
		{
			// 3 //
			Environment:  "default",
			ProvEnv:      "sandbox",
			ProvVariable: "https://koding.com/default/version.txt",
			Exp:          "https://koding.com/development/version.txt",
			ExpNoManaged: "https://koding.com/development/version.txt",
		},
		{
			// 4 //
			Environment:  "production",
			ProvEnv:      "production",
			ProvVariable: "https://koding.com/production/version.txt",
			Exp:          "https://koding.com/production/version.txt",
			ExpNoManaged: "https://koding.com/production/version.txt",
		},
		{
			// 5 //
			Environment:  "development",
			ProvEnv:      "devmanaged",
			ProvVariable: "https://koding.com/development/version.txt",
			Exp:          "https://koding.com/devmanaged/version.txt",
			ExpNoManaged: "https://koding.com/development/version.txt",
		},
		{
			// 6 //
			Environment:  "default",
			ProvEnv:      "devmanaged",
			ProvVariable: "https://koding.com/default/version.txt",
			Exp:          "https://koding.com/devmanaged/version.txt",
			ExpNoManaged: "https://koding.com/development/version.txt",
		},
	}

	for i, test := range tests {
		t.Run(fmt.Sprintf("test_no_%d", i), func(t *testing.T) {
			provVariable := NewEndpoint(test.ProvVariable)
			exp := NewEndpoint(test.Exp)
			expNoManaged := NewEndpoint(test.ExpNoManaged)

			// Temporarily replace buildin environment. This also means that you
			// should not run these test in parallel!
			var envcopy = environment
			environment = test.Environment
			defer func() {
				environment = envcopy
			}()

			if e := ReplaceEnv(provVariable, test.ProvEnv); !e.Equal(exp) {
				t.Fatalf("want string = %#v; got %#v", test.Exp, e)
			}

			if e := ReplaceEnv(provVariable, RmManaged(test.ProvEnv)); !e.Equal(expNoManaged) {
				t.Fatalf("want string = %#v; got %#v", test.Exp, e)
			}
		})
	}
}

func TestEndpointEqual(t *testing.T) {
	good := mustURL("http://127.0.0.1:56789/kite")
	bad := mustURL("http://127.0.0.1")

	cases := map[string]struct {
		lhs *Endpoint
		rhs *Endpoint
		ok  bool
	}{
		"empty endpoints": {
			&Endpoint{},
			&Endpoint{},
			true,
		},
		"public private match": {
			&Endpoint{
				Public:  good,
				Private: good,
			},
			&Endpoint{
				Public:  good,
				Private: good,
			},
			true,
		},
		"public match": {
			&Endpoint{
				Public: good,
			},
			&Endpoint{
				Public: good,
			},
			true,
		},
		"private match": {
			&Endpoint{
				Private: good,
			},
			&Endpoint{
				Private: good,
			},
			true,
		},
		"public private no match": {
			&Endpoint{
				Public:  good,
				Private: bad,
			},
			&Endpoint{
				Public:  bad,
				Private: good,
			},
			false,
		},
		"public no match": {
			&Endpoint{
				Public: good,
			},
			&Endpoint{
				Public: bad,
			},
			false,
		},
		"private no match": {
			&Endpoint{
				Private: good,
			},
			&Endpoint{
				Private: bad,
			},
			false,
		},
		"public no match private match": {
			&Endpoint{
				Public:  good,
				Private: good,
			},
			&Endpoint{
				Public:  bad,
				Private: good,
			},
			false,
		},
		"private no match public match": {
			&Endpoint{
				Public:  good,
				Private: good,
			},
			&Endpoint{
				Public:  bad,
				Private: good,
			},
			false,
		},
	}

	for name, cas := range cases {
		// capture range variable here
		cas := cas
		t.Run(name, func(t *testing.T) {
			t.Parallel()
			if ok := cas.lhs.Equal(cas.rhs); ok != cas.ok {
				t.Fatalf("got %t, want %t", ok, cas.ok)
			}
		})
	}
}

func TestURLCopy(t *testing.T) {
	cases := map[string]*URL{
		"nil url":            nil,
		"nil underlying url": {URL: nil},
		"simple url":         {URL: &url.URL{Scheme: "http", Host: "example.com"}},
		"url with user":      {URL: &url.URL{Scheme: "http", Host: "example.com", User: url.UserPassword("user", "pass")}},
	}

	for name, u := range cases {
		// capture range variable here
		u := u
		t.Run(name, func(t *testing.T) {
			t.Parallel()
			uCopy := u.Copy()

			if u.IsNil() {
				if uCopy != nil {
					t.Errorf("want uCopy to be nil; got %#v", uCopy)
				}

				return
			}

			modifyURL(uCopy)

			if reflect.DeepEqual(uCopy, u) {
				t.Errorf("want %#v != %#v", uCopy, u)
			}
		})
	}
}

func modifyURL(u *URL) {
	if u.IsNil() {
		return
	}

	u.URL.Host = utils.RandomString()

	if u.URL.User != nil {
		u.URL.User = url.UserPassword(utils.RandomString(), "")
	}
}

func TestEndpointCopy(t *testing.T) {
	url := &URL{URL: &url.URL{Scheme: "http", Host: "example.com", User: url.UserPassword("user", "pass")}}

	cases := map[string]*Endpoint{
		"nil endpoint":          nil,
		"nil underlying urls":   {Private: nil, Public: nil},
		"private-only endpoint": {Private: url},
		"public-only endpoint":  {Public: url},
		"endpoint":              {Private: url, Public: url},
	}

	for name, e := range cases {
		// capture range variable here
		e := e
		t.Run(name, func(t *testing.T) {
			t.Parallel()
			eCopy := e.Copy()

			if e.IsNil() {
				if eCopy != nil {
					t.Errorf("want eCopy to be nil; got %#v", eCopy)
				}

				return
			}

			modifyEndpoint(eCopy)

			if reflect.DeepEqual(eCopy, e) {
				t.Errorf("want %#v != %#v", eCopy, e)
			}
		})
	}
}

func modifyEndpoint(e *Endpoint) {
	if e.IsNil() {
		return
	}

	if !e.Public.IsNil() {
		modifyURL(e.Public)
	}

	if !e.Private.IsNil() {
		modifyURL(e.Private)
	}
}
<commit_msg>kites/config: fix test<commit_after>package config

import (
	"fmt"
	"net/url"
	"reflect"
	"testing"

	"koding/tools/utils"
)

func TestReplaceEnv(t *testing.T) {
	tests := []struct {
		Environment  string
		ProvEnv      string
		ProvVariable string
		Exp          string
		ExpNoManaged string
	}{
		{
			// 0 //
			Environment:  "sandbox",
			ProvEnv:      "sandbox",
			ProvVariable: "https://koding.com/sandbox/version.txt",
			Exp:          "https://koding.com/development/version.txt",
			ExpNoManaged: "https://koding.com/development/version.txt",
		},
		{
			// 1 //
			Environment:  "production",
			ProvEnv:      "managed",
			ProvVariable: "https://koding.com/production/version.txt",
			Exp:          "https://koding.com/managed/version.txt",
			ExpNoManaged: "https://koding.com/production/version.txt",
		},
		{
			// 2 //
			Environment:  "production",
			ProvEnv:      "devmanaged",
			ProvVariable: "https://koding.com/production/version.txt",
			Exp:          "https://koding.com/devmanaged/version.txt",
			ExpNoManaged: "https://koding.com/development/version.txt",
		},
		{
			// 3 //
			Environment:  "default",
			ProvEnv:      "sandbox",
			ProvVariable: "https://koding.com/default/version.txt",
			Exp:          "https://koding.com/development/version.txt",
			ExpNoManaged: "https://koding.com/development/version.txt",
		},
		{
			// 4 //
			Environment:  "production",
			ProvEnv:      "production",
			ProvVariable: "https://koding.com/production/version.txt",
			Exp:          "https://koding.com/production/version.txt",
			ExpNoManaged: "https://koding.com/production/version.txt",
		},
		{
			// 5 //
			Environment:  "development",
			ProvEnv:      "devmanaged",
			ProvVariable: "https://koding.com/development/version.txt",
			Exp:          "https://koding.com/devmanaged/version.txt",
			ExpNoManaged: "https://koding.com/development/version.txt",
		},
		{
			// 6 //
			Environment:  "default",
			ProvEnv:      "devmanaged",
			ProvVariable: "https://koding.com/default/version.txt",
			Exp:          "https://koding.com/devmanaged/version.txt",
			ExpNoManaged: "https://koding.com/development/version.txt",
		},
	}

	for i, test := range tests {
		t.Run(fmt.Sprintf("test_no_%d", i), func(t *testing.T) {
			provVariable := NewEndpoint(test.ProvVariable)
			exp := NewEndpoint(test.Exp)
			expNoManaged := NewEndpoint(test.ExpNoManaged)

			if e := ReplaceCustomEnv(provVariable, test.Environment, test.ProvEnv); !e.Equal(exp) {
				t.Fatalf("want %s; got %s", test.Exp, e)
			}

			if e := ReplaceCustomEnv(provVariable, test.Environment, RmManaged(test.ProvEnv)); !e.Equal(expNoManaged) {
				t.Fatalf("want %s; got %s", test.Exp, e)
			}
		})
	}
}

func TestEndpointEqual(t *testing.T) {
	good := mustURL("http://127.0.0.1:56789/kite")
	bad := mustURL("http://127.0.0.1")

	cases := map[string]struct {
		lhs *Endpoint
		rhs *Endpoint
		ok  bool
	}{
		"empty endpoints": {
			&Endpoint{},
			&Endpoint{},
			true,
		},
		"public private match": {
			&Endpoint{
				Public:  good,
				Private: good,
			},
			&Endpoint{
				Public:  good,
				Private: good,
			},
			true,
		},
		"public match": {
			&Endpoint{
				Public: good,
			},
			&Endpoint{
				Public: good,
			},
			true,
		},
		"private match": {
			&Endpoint{
				Private: good,
			},
			&Endpoint{
				Private: good,
			},
			true,
		},
		"public private no match": {
			&Endpoint{
				Public:  good,
				Private: bad,
			},
			&Endpoint{
				Public:  bad,
				Private: good,
			},
			false,
		},
		"public no match": {
			&Endpoint{
				Public: good,
			},
			&Endpoint{
				Public: bad,
			},
			false,
		},
		"private no match": {
			&Endpoint{
				Private: good,
			},
			&Endpoint{
				Private: bad,
			},
			false,
		},
		"public no match private match": {
			&Endpoint{
				Public:  good,
				Private: good,
			},
			&Endpoint{
				Public:  bad,
				Private: good,
			},
			false,
		},
		"private no match public match": {
			&Endpoint{
				Public:  good,
				Private: good,
			},
			&Endpoint{
				Public:  bad,
				Private: good,
			},
			false,
		},
	}

	for name, cas := range cases {
		// capture range variable here
		cas := cas
		t.Run(name, func(t *testing.T) {
			t.Parallel()
			if ok := cas.lhs.Equal(cas.rhs); ok != cas.ok {
				t.Fatalf("got %t, want %t", ok, cas.ok)
			}
		})
	}
}

func TestURLCopy(t *testing.T) {
	cases := map[string]*URL{
		"nil url":            nil,
		"nil underlying url": {URL: nil},
		"simple url":         {URL: &url.URL{Scheme: "http", Host: "example.com"}},
		"url with user":      {URL: &url.URL{Scheme: "http", Host: "example.com", User: url.UserPassword("user", "pass")}},
	}

	for name, u := range cases {
		// capture range variable here
		u := u
		t.Run(name, func(t *testing.T) {
			t.Parallel()
			uCopy := u.Copy()

			if u.IsNil() {
				if uCopy != nil {
					t.Errorf("want uCopy to be nil; got %#v", uCopy)
				}

				return
			}

			modifyURL(uCopy)

			if reflect.DeepEqual(uCopy, u) {
				t.Errorf("want %#v != %#v", uCopy, u)
			}
		})
	}
}

func modifyURL(u *URL) {
	if u.IsNil() {
		return
	}

	u.URL.Host = utils.RandomString()

	if u.URL.User != nil {
		u.URL.User = url.UserPassword(utils.RandomString(), "")
	}
}

func TestEndpointCopy(t *testing.T) {
	url := &URL{URL: &url.URL{Scheme: "http", Host: "example.com", User: url.UserPassword("user", "pass")}}

	cases := map[string]*Endpoint{
		"nil endpoint":          nil,
		"nil underlying urls":   {Private: nil, Public: nil},
		"private-only endpoint": {Private: url},
		"public-only endpoint":  {Public: url},
		"endpoint":              {Private: url, Public: url},
	}

	for name, e := range cases {
		// capture range variable here
		e := e
		t.Run(name, func(t *testing.T) {
			t.Parallel()
			eCopy := e.Copy()

			if e.IsNil() {
				if eCopy != nil {
					t.Errorf("want eCopy to be nil; got %#v", eCopy)
				}

				return
			}

			modifyEndpoint(eCopy)

			if reflect.DeepEqual(eCopy, e) {
				t.Errorf("want %#v != %#v", eCopy, e)
			}
		})
	}
}

func modifyEndpoint(e *Endpoint) {
	if e.IsNil() {
		return
	}

	if !e.Public.IsNil() {
		modifyURL(e.Public)
	}

	if !e.Private.IsNil() {
		modifyURL(e.Private)
	}
}
<|endoftext|>


<commit_before>package models

import (
	"errors"
	"fmt"
	"socialapi/config"
	"socialapi/request"
	"time"

	"github.com/VerbalExpressions/GoVerbalExpressions"
	"github.com/jinzhu/gorm"
	"github.com/koding/bongo"
)

var mentionRegex = verbalexpressions.New().
	Find("@").
	BeginCapture().
	Word().
	EndCapture().
	Regex()

type ChannelMessage struct {
	// unique identifier of the channel message
	Id int64 `json:"id,string"`

	// Body of the mesage
	Body string `json:"body"`

	// Generated Slug for body
	Slug string `json:"slug"                               sql:"NOT NULL;TYPE:VARCHAR(100);"`

	// type of the message
	TypeConstant string `json:"typeConstant"               sql:"NOT NULL;TYPE:VARCHAR(100);"`

	// Creator of the channel message
	AccountId int64 `json:"accountId,string"               sql:"NOT NULL"`

	// in which channel this message is created
	InitialChannelId int64 `json:"initialChannelId,string" sql:"NOT NULL"`

	// holds troll, unsafe, etc
	MetaBits int16 `json:"-"`

	// Creation date of the message
	CreatedAt time.Time `json:"createdAt"                  sql:"DEFAULT:CURRENT_TIMESTAMP"`

	// Modification date of the message
	UpdatedAt time.Time `json:"updatedAt"                  sql:"DEFAULT:CURRENT_TIMESTAMP"`

	// Deletion date of the channel message
	DeletedAt time.Time `json:"deletedAt"`

	// Extra data storage
	Payload gorm.Hstore `json:"payload,omitempty"`
}

func (c *ChannelMessage) BeforeCreate() {
	if res, err := c.isExemptContent(); err == nil && res {
		c.MetaBits = updateTrollModeBit(c.MetaBits)
	}

	c.DeletedAt = ZeroDate()
}

func (c *ChannelMessage) BeforeUpdate() {
	if res, err := c.isExemptContent(); err == nil && res {
		c.MetaBits = updateTrollModeBit(c.MetaBits)
	}

	c.DeletedAt = ZeroDate()
}

func (c *ChannelMessage) AfterCreate() {
	bongo.B.AfterCreate(c)
}

func (c *ChannelMessage) AfterUpdate() {
	bongo.B.AfterUpdate(c)
}

func (c ChannelMessage) AfterDelete() {
	bongo.B.AfterDelete(c)
}

func (c ChannelMessage) GetId() int64 {
	return c.Id
}

func (c ChannelMessage) TableName() string {
	return "api.channel_message"
}

const (
	ChannelMessage_TYPE_POST            = "post"
	ChannelMessage_TYPE_REPLY           = "reply"
	ChannelMessage_TYPE_JOIN            = "join"
	ChannelMessage_TYPE_LEAVE           = "leave"
	ChannelMessage_TYPE_CHAT            = "chat"
	ChannelMessage_TYPE_PRIVATE_MESSAGE = "privatemessage"
)

func NewChannelMessage() *ChannelMessage {
	return &ChannelMessage{}
}

func (c *ChannelMessage) ById(id int64) error {
	return bongo.B.ById(c, id)
}

func (c *ChannelMessage) One(q *bongo.Query) error {
	return bongo.B.One(c, c, q)
}

func (c *ChannelMessage) Some(data interface{}, q *bongo.Query) error {
	return bongo.B.Some(c, data, q)
}

func (c *ChannelMessage) UpdateMulti(rest ...map[string]interface{}) error {
	return bongo.B.UpdateMulti(c, rest...)
}

func (c *ChannelMessage) CountWithQuery(q *bongo.Query) (int, error) {
	return bongo.B.CountWithQuery(c, q)
}

func (c *ChannelMessage) isExemptContent() (bool, error) {
	// set meta bits if only message is post or a reply
	if c.TypeConstant != ChannelMessage_TYPE_POST &&
		c.TypeConstant != ChannelMessage_TYPE_REPLY {
		return false, nil
	}

	if c.AccountId == 0 && c.Id != 0 {
		if err := c.ById(c.Id); err != nil {
			return false, err
		}
	} else {
		return false, fmt.Errorf("Couldnt find accountId from content %+v", c)
	}

	account, err := FetchAccountFromCache(c.AccountId)
	if err != nil {
		return false, err
	}

	if account == nil {
		return false, fmt.Errorf("Account is nil, accountId:%d", c.AccountId)
	}

	if account.IsTroll {
		return true, nil
	}

	return false, nil
}

func bodyLenCheck(body string) error {
	if len(body) < config.Get().Limits.MessageBodyMinLen {
		return fmt.Errorf("Message Body Length should be greater than %d, yours is %d ", config.Get().Limits.MessageBodyMinLen, len(body))
	}

	return nil
}

// todo create a new message while updating the channel_message and delete other
// cases, since deletion is a soft delete, old instances will still be there
func (c *ChannelMessage) Update() error {
	if err := bodyLenCheck(c.Body); err != nil {
		return err
	}
	// only update body
	err := bongo.B.UpdatePartial(c,
		map[string]interface{}{
			"body": c.Body,
		},
	)
	return err
}

func (c *ChannelMessage) Create() error {
	if err := bodyLenCheck(c.Body); err != nil {
		return err
	}

	var err error
	c, err = Slugify(c)
	if err != nil {
		return err
	}

	return bongo.B.Create(c)
}

// CreateRaw creates a new channel message without effected by auto generated createdAt
// and updatedAt values
func (c *ChannelMessage) CreateRaw() error {
	insertSql := "INSERT INTO " +
		c.TableName() +
		` ("body","slug","type_constant","account_id","initial_channel_id",` +
		`"created_at","updated_at","deleted_at","payload") ` +
		"VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9) " +
		"RETURNING ID"

	return bongo.B.DB.CommonDB().QueryRow(insertSql, c.Body, c.Slug, c.TypeConstant, c.AccountId, c.InitialChannelId,
		c.CreatedAt, c.UpdatedAt, c.DeletedAt, c.Payload).Scan(&c.Id)
}

// UpdateBodyRaw updates message body without effecting createdAt/UpdatedAt
// timestamps
func (c *ChannelMessage) UpdateBodyRaw() error {
	updateSql := fmt.Sprintf("UPDATE %s SET body=? WHERE id=?", c.TableName())

	return bongo.B.DB.Exec(updateSql, c.Body, c.Id).Error
}

func (c *ChannelMessage) Delete() error {
	return bongo.B.Delete(c)
}

func (c *ChannelMessage) FetchByIds(ids []int64) ([]ChannelMessage, error) {
	var messages []ChannelMessage

	if len(ids) == 0 {
		return messages, nil
	}

	if err := bongo.B.FetchByIds(c, &messages, ids); err != nil {
		return nil, err
	}

	return messages, nil
}

func (c *ChannelMessage) BuildMessages(query *request.Query, messages []ChannelMessage) ([]*ChannelMessageContainer, error) {
	containers := make([]*ChannelMessageContainer, len(messages))
	if len(containers) == 0 {
		return containers, nil
	}

	for i, message := range messages {
		d := NewChannelMessage()
		*d = message
		data, err := d.BuildMessage(query)
		if err != nil {
			return containers, err
		}
		containers[i] = data
	}

	return containers, nil
}

func (c *ChannelMessage) BuildMessage(query *request.Query) (*ChannelMessageContainer, error) {
	cmc, err := c.FetchRelatives(query)
	if err != nil {
		return nil, err
	}

	mr := NewMessageReply()
	mr.MessageId = c.Id
	q := query
	q.Limit = 3
	replies, err := mr.List(query)
	if err != nil {
		return nil, err
	}

	repliesCount, err := mr.Count()
	if err != nil {
		return nil, err
	}
	cmc.RepliesCount = repliesCount

	cmc.IsFollowed, err = c.CheckIsMessageFollowed(query)
	if err != nil {
		return nil, err
	}

	populatedChannelMessagesReplies := make([]*ChannelMessageContainer, len(replies))
	for rl := 0; rl < len(replies); rl++ {
		cmrc, err := replies[rl].FetchRelatives(query)
		if err != nil {
			return nil, err
		}
		populatedChannelMessagesReplies[rl] = cmrc
	}

	cmc.Replies = populatedChannelMessagesReplies
	return cmc, nil
}

func (c *ChannelMessage) CheckIsMessageFollowed(query *request.Query) (bool, error) {
	channel := NewChannel()
	if err := channel.FetchPinnedActivityChannel(query.AccountId, query.GroupName); err != nil {
		if err == bongo.RecordNotFound {
			return false, nil
		}
		return false, err
	}

	cml := NewChannelMessageList()
	q := &bongo.Query{
		Selector: map[string]interface{}{
			"channel_id": channel.Id,
			"message_id": c.Id,
		},
	}
	if err := cml.One(q); err != nil {
		if err == bongo.RecordNotFound {
			return false, nil
		}

		return false, err
	}

	return true, nil
}

func (c *ChannelMessage) BuildEmptyMessageContainer() (*ChannelMessageContainer, error) {
	if c.Id == 0 {
		return nil, errors.New("Channel message id is not set")
	}
	container := NewChannelMessageContainer()
	container.Message = c

	oldId, err := FetchAccountOldIdByIdFromCache(c.AccountId)
	if err != nil {
		return nil, err
	}

	container.AccountOldId = oldId

	interactionContainer := NewInteractionContainer()
	interactionContainer.ActorsPreview = make([]string, 0)
	interactionContainer.IsInteracted = false
	interactionContainer.ActorsCount = 0

	container.Interactions = make(map[string]*InteractionContainer)
	container.Interactions["like"] = interactionContainer

	return container, nil
}

func (c *ChannelMessage) FetchRelatives(query *request.Query) (*ChannelMessageContainer, error) {
	container, err := c.BuildEmptyMessageContainer()
	if err != nil {
		return nil, err
	}

	i := NewInteraction()
	i.MessageId = c.Id

	// get preview
	query.Type = "like"
	query.Limit = 3
	interactorIds, err := i.List(query)
	if err != nil {
		return nil, err
	}

	oldIds, err := FetchOldIdsByAccountIds(interactorIds)
	if err != nil {
		return nil, err
	}

	interactionContainer := NewInteractionContainer()
	interactionContainer.ActorsPreview = oldIds

	// check if the current user is interacted in this thread
	isInteracted, err := i.IsInteracted(query.AccountId)
	if err != nil {
		return nil, err
	}

	interactionContainer.IsInteracted = isInteracted

	// fetch interaction count
	count, err := i.Count(query.Type)
	if err != nil {
		return nil, err
	}

	interactionContainer.ActorsCount = count

	container.Interactions["like"] = interactionContainer
	return container, nil
}

func generateMessageListQuery(channelId int64, q *request.Query) *bongo.Query {
	messageType := q.Type
	if messageType == "" {
		messageType = ChannelMessage_TYPE_POST
	}

	return &bongo.Query{
		Selector: map[string]interface{}{
			"account_id":         q.AccountId,
			"initial_channel_id": channelId,
			"type_constant":      messageType,
		},
		Pagination: *bongo.NewPagination(q.Limit, q.Skip),
		Sort: map[string]string{
			"created_at": "DESC",
		},
	}
}

func (c *ChannelMessage) FetchMessagesByChannelId(channelId int64, q *request.Query) ([]ChannelMessage, error) {
	query := generateMessageListQuery(channelId, q)

	var messages []ChannelMessage
	if err := c.Some(&messages, query); err != nil {
		return nil, err
	}

	if messages == nil {
		return make([]ChannelMessage, 0), nil
	}
	return messages, nil
}

func (c *ChannelMessage) GetMentionedUsernames() []string {
	flattened := make([]string, 0)

	res := mentionRegex.FindAllStringSubmatch(c.Body, -1)
	if len(res) == 0 {
		return flattened
	}

	participants := map[string]struct{}{}
	// remove duplicate mentions
	for _, ele := range res {
		participants[ele[1]] = struct{}{}
	}

	for participant := range participants {
		flattened = append(flattened, participant)
	}

	return flattened
}

func (c *ChannelMessage) FetchTotalMessageCount(q *request.Query) (int, error) {
	query := &bongo.Query{
		Selector: map[string]interface{}{
			"account_id":    q.AccountId,
			"type_constant": q.Type,
		},
		Pagination: *bongo.NewPagination(q.Limit, q.Skip),
	}

	return c.CountWithQuery(query)
}

func (c *ChannelMessage) FetchMessageIds(q *request.Query) ([]int64, error) {
	query := &bongo.Query{
		Selector: map[string]interface{}{
			"account_id":    q.AccountId,
			"type_constant": q.Type,
		},
		Pluck:      "id",
		Pagination: *bongo.NewPagination(q.Limit, q.Skip),
		Sort: map[string]string{
			"created_at": "DESC",
		},
	}

	var messageIds []int64
	if err := c.Some(&messageIds, query); err != nil {
		return nil, err
	}

	if messageIds == nil {
		return make([]int64, 0), nil
	}

	return messageIds, nil
}

// BySlug fetchs channel message by its slug
// checks if message is in the channel or not
func (c *ChannelMessage) BySlug(query *request.Query) error {
	if query.Slug == "" {
		return errors.New("slug is not set")
	}

	// fetch message itself
	q := &bongo.Query{
		Selector: map[string]interface{}{
			"slug": query.Slug,
		},
	}

	if err := c.One(q); err != nil {
		return nil
	}

	// fetch channel by group name
	query.Name = query.GroupName
	query.Type = Channel_TYPE_GROUP
	ch := NewChannel()
	channel, err := ch.ByName(query)
	if err != nil {
		return err
	}

	if channel.Id == 0 {
		return errors.New("channel is not found")
	}

	// check if message is in the channel
	cml := NewChannelMessageList()
	res, err := cml.IsInChannel(c.Id, channel.Id)
	if err != nil {
		return err
	}

	// if message is not in the channel
	if !res {
		return bongo.RecordNotFound
	}

	return nil
}
<commit_msg>Sitemap: Fix error return value of BySlug method<commit_after>package models

import (
	"errors"
	"fmt"
	"socialapi/config"
	"socialapi/request"
	"time"

	"github.com/VerbalExpressions/GoVerbalExpressions"
	"github.com/jinzhu/gorm"
	"github.com/koding/bongo"
)

var mentionRegex = verbalexpressions.New().
	Find("@").
	BeginCapture().
	Word().
	EndCapture().
	Regex()

type ChannelMessage struct {
	// unique identifier of the channel message
	Id int64 `json:"id,string"`

	// Body of the mesage
	Body string `json:"body"`

	// Generated Slug for body
	Slug string `json:"slug"                               sql:"NOT NULL;TYPE:VARCHAR(100);"`

	// type of the message
	TypeConstant string `json:"typeConstant"               sql:"NOT NULL;TYPE:VARCHAR(100);"`

	// Creator of the channel message
	AccountId int64 `json:"accountId,string"               sql:"NOT NULL"`

	// in which channel this message is created
	InitialChannelId int64 `json:"initialChannelId,string" sql:"NOT NULL"`

	// holds troll, unsafe, etc
	MetaBits int16 `json:"-"`

	// Creation date of the message
	CreatedAt time.Time `json:"createdAt"                  sql:"DEFAULT:CURRENT_TIMESTAMP"`

	// Modification date of the message
	UpdatedAt time.Time `json:"updatedAt"                  sql:"DEFAULT:CURRENT_TIMESTAMP"`

	// Deletion date of the channel message
	DeletedAt time.Time `json:"deletedAt"`

	// Extra data storage
	Payload gorm.Hstore `json:"payload,omitempty"`
}

func (c *ChannelMessage) BeforeCreate() {
	if res, err := c.isExemptContent(); err == nil && res {
		c.MetaBits = updateTrollModeBit(c.MetaBits)
	}

	c.DeletedAt = ZeroDate()
}

func (c *ChannelMessage) BeforeUpdate() {
	if res, err := c.isExemptContent(); err == nil && res {
		c.MetaBits = updateTrollModeBit(c.MetaBits)
	}

	c.DeletedAt = ZeroDate()
}

func (c *ChannelMessage) AfterCreate() {
	bongo.B.AfterCreate(c)
}

func (c *ChannelMessage) AfterUpdate() {
	bongo.B.AfterUpdate(c)
}

func (c ChannelMessage) AfterDelete() {
	bongo.B.AfterDelete(c)
}

func (c ChannelMessage) GetId() int64 {
	return c.Id
}

func (c ChannelMessage) TableName() string {
	return "api.channel_message"
}

const (
	ChannelMessage_TYPE_POST            = "post"
	ChannelMessage_TYPE_REPLY           = "reply"
	ChannelMessage_TYPE_JOIN            = "join"
	ChannelMessage_TYPE_LEAVE           = "leave"
	ChannelMessage_TYPE_CHAT            = "chat"
	ChannelMessage_TYPE_PRIVATE_MESSAGE = "privatemessage"
)

func NewChannelMessage() *ChannelMessage {
	return &ChannelMessage{}
}

func (c *ChannelMessage) ById(id int64) error {
	return bongo.B.ById(c, id)
}

func (c *ChannelMessage) One(q *bongo.Query) error {
	return bongo.B.One(c, c, q)
}

func (c *ChannelMessage) Some(data interface{}, q *bongo.Query) error {
	return bongo.B.Some(c, data, q)
}

func (c *ChannelMessage) UpdateMulti(rest ...map[string]interface{}) error {
	return bongo.B.UpdateMulti(c, rest...)
}

func (c *ChannelMessage) CountWithQuery(q *bongo.Query) (int, error) {
	return bongo.B.CountWithQuery(c, q)
}

func (c *ChannelMessage) isExemptContent() (bool, error) {
	// set meta bits if only message is post or a reply
	if c.TypeConstant != ChannelMessage_TYPE_POST &&
		c.TypeConstant != ChannelMessage_TYPE_REPLY {
		return false, nil
	}

	if c.AccountId == 0 && c.Id != 0 {
		if err := c.ById(c.Id); err != nil {
			return false, err
		}
	} else {
		return false, fmt.Errorf("Couldnt find accountId from content %+v", c)
	}

	account, err := FetchAccountFromCache(c.AccountId)
	if err != nil {
		return false, err
	}

	if account == nil {
		return false, fmt.Errorf("Account is nil, accountId:%d", c.AccountId)
	}

	if account.IsTroll {
		return true, nil
	}

	return false, nil
}

func bodyLenCheck(body string) error {
	if len(body) < config.Get().Limits.MessageBodyMinLen {
		return fmt.Errorf("Message Body Length should be greater than %d, yours is %d ", config.Get().Limits.MessageBodyMinLen, len(body))
	}

	return nil
}

// todo create a new message while updating the channel_message and delete other
// cases, since deletion is a soft delete, old instances will still be there
func (c *ChannelMessage) Update() error {
	if err := bodyLenCheck(c.Body); err != nil {
		return err
	}
	// only update body
	err := bongo.B.UpdatePartial(c,
		map[string]interface{}{
			"body": c.Body,
		},
	)
	return err
}

func (c *ChannelMessage) Create() error {
	if err := bodyLenCheck(c.Body); err != nil {
		return err
	}

	var err error
	c, err = Slugify(c)
	if err != nil {
		return err
	}

	return bongo.B.Create(c)
}

// CreateRaw creates a new channel message without effected by auto generated createdAt
// and updatedAt values
func (c *ChannelMessage) CreateRaw() error {
	insertSql := "INSERT INTO " +
		c.TableName() +
		` ("body","slug","type_constant","account_id","initial_channel_id",` +
		`"created_at","updated_at","deleted_at","payload") ` +
		"VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9) " +
		"RETURNING ID"

	return bongo.B.DB.CommonDB().QueryRow(insertSql, c.Body, c.Slug, c.TypeConstant, c.AccountId, c.InitialChannelId,
		c.CreatedAt, c.UpdatedAt, c.DeletedAt, c.Payload).Scan(&c.Id)
}

// UpdateBodyRaw updates message body without effecting createdAt/UpdatedAt
// timestamps
func (c *ChannelMessage) UpdateBodyRaw() error {
	updateSql := fmt.Sprintf("UPDATE %s SET body=? WHERE id=?", c.TableName())

	return bongo.B.DB.Exec(updateSql, c.Body, c.Id).Error
}

func (c *ChannelMessage) Delete() error {
	return bongo.B.Delete(c)
}

func (c *ChannelMessage) FetchByIds(ids []int64) ([]ChannelMessage, error) {
	var messages []ChannelMessage

	if len(ids) == 0 {
		return messages, nil
	}

	if err := bongo.B.FetchByIds(c, &messages, ids); err != nil {
		return nil, err
	}

	return messages, nil
}

func (c *ChannelMessage) BuildMessages(query *request.Query, messages []ChannelMessage) ([]*ChannelMessageContainer, error) {
	containers := make([]*ChannelMessageContainer, len(messages))
	if len(containers) == 0 {
		return containers, nil
	}

	for i, message := range messages {
		d := NewChannelMessage()
		*d = message
		data, err := d.BuildMessage(query)
		if err != nil {
			return containers, err
		}
		containers[i] = data
	}

	return containers, nil
}

func (c *ChannelMessage) BuildMessage(query *request.Query) (*ChannelMessageContainer, error) {
	cmc, err := c.FetchRelatives(query)
	if err != nil {
		return nil, err
	}

	mr := NewMessageReply()
	mr.MessageId = c.Id
	q := query
	q.Limit = 3
	replies, err := mr.List(query)
	if err != nil {
		return nil, err
	}

	repliesCount, err := mr.Count()
	if err != nil {
		return nil, err
	}
	cmc.RepliesCount = repliesCount

	cmc.IsFollowed, err = c.CheckIsMessageFollowed(query)
	if err != nil {
		return nil, err
	}

	populatedChannelMessagesReplies := make([]*ChannelMessageContainer, len(replies))
	for rl := 0; rl < len(replies); rl++ {
		cmrc, err := replies[rl].FetchRelatives(query)
		if err != nil {
			return nil, err
		}
		populatedChannelMessagesReplies[rl] = cmrc
	}

	cmc.Replies = populatedChannelMessagesReplies
	return cmc, nil
}

func (c *ChannelMessage) CheckIsMessageFollowed(query *request.Query) (bool, error) {
	channel := NewChannel()
	if err := channel.FetchPinnedActivityChannel(query.AccountId, query.GroupName); err != nil {
		if err == bongo.RecordNotFound {
			return false, nil
		}
		return false, err
	}

	cml := NewChannelMessageList()
	q := &bongo.Query{
		Selector: map[string]interface{}{
			"channel_id": channel.Id,
			"message_id": c.Id,
		},
	}
	if err := cml.One(q); err != nil {
		if err == bongo.RecordNotFound {
			return false, nil
		}

		return false, err
	}

	return true, nil
}

func (c *ChannelMessage) BuildEmptyMessageContainer() (*ChannelMessageContainer, error) {
	if c.Id == 0 {
		return nil, errors.New("Channel message id is not set")
	}
	container := NewChannelMessageContainer()
	container.Message = c

	oldId, err := FetchAccountOldIdByIdFromCache(c.AccountId)
	if err != nil {
		return nil, err
	}

	container.AccountOldId = oldId

	interactionContainer := NewInteractionContainer()
	interactionContainer.ActorsPreview = make([]string, 0)
	interactionContainer.IsInteracted = false
	interactionContainer.ActorsCount = 0

	container.Interactions = make(map[string]*InteractionContainer)
	container.Interactions["like"] = interactionContainer

	return container, nil
}

func (c *ChannelMessage) FetchRelatives(query *request.Query) (*ChannelMessageContainer, error) {
	container, err := c.BuildEmptyMessageContainer()
	if err != nil {
		return nil, err
	}

	i := NewInteraction()
	i.MessageId = c.Id

	// get preview
	query.Type = "like"
	query.Limit = 3
	interactorIds, err := i.List(query)
	if err != nil {
		return nil, err
	}

	oldIds, err := FetchOldIdsByAccountIds(interactorIds)
	if err != nil {
		return nil, err
	}

	interactionContainer := NewInteractionContainer()
	interactionContainer.ActorsPreview = oldIds

	// check if the current user is interacted in this thread
	isInteracted, err := i.IsInteracted(query.AccountId)
	if err != nil {
		return nil, err
	}

	interactionContainer.IsInteracted = isInteracted

	// fetch interaction count
	count, err := i.Count(query.Type)
	if err != nil {
		return nil, err
	}

	interactionContainer.ActorsCount = count

	container.Interactions["like"] = interactionContainer
	return container, nil
}

func generateMessageListQuery(channelId int64, q *request.Query) *bongo.Query {
	messageType := q.Type
	if messageType == "" {
		messageType = ChannelMessage_TYPE_POST
	}

	return &bongo.Query{
		Selector: map[string]interface{}{
			"account_id":         q.AccountId,
			"initial_channel_id": channelId,
			"type_constant":      messageType,
		},
		Pagination: *bongo.NewPagination(q.Limit, q.Skip),
		Sort: map[string]string{
			"created_at": "DESC",
		},
	}
}

func (c *ChannelMessage) FetchMessagesByChannelId(channelId int64, q *request.Query) ([]ChannelMessage, error) {
	query := generateMessageListQuery(channelId, q)

	var messages []ChannelMessage
	if err := c.Some(&messages, query); err != nil {
		return nil, err
	}

	if messages == nil {
		return make([]ChannelMessage, 0), nil
	}
	return messages, nil
}

func (c *ChannelMessage) GetMentionedUsernames() []string {
	flattened := make([]string, 0)

	res := mentionRegex.FindAllStringSubmatch(c.Body, -1)
	if len(res) == 0 {
		return flattened
	}

	participants := map[string]struct{}{}
	// remove duplicate mentions
	for _, ele := range res {
		participants[ele[1]] = struct{}{}
	}

	for participant := range participants {
		flattened = append(flattened, participant)
	}

	return flattened
}

func (c *ChannelMessage) FetchTotalMessageCount(q *request.Query) (int, error) {
	query := &bongo.Query{
		Selector: map[string]interface{}{
			"account_id":    q.AccountId,
			"type_constant": q.Type,
		},
		Pagination: *bongo.NewPagination(q.Limit, q.Skip),
	}

	return c.CountWithQuery(query)
}

func (c *ChannelMessage) FetchMessageIds(q *request.Query) ([]int64, error) {
	query := &bongo.Query{
		Selector: map[string]interface{}{
			"account_id":    q.AccountId,
			"type_constant": q.Type,
		},
		Pluck:      "id",
		Pagination: *bongo.NewPagination(q.Limit, q.Skip),
		Sort: map[string]string{
			"created_at": "DESC",
		},
	}

	var messageIds []int64
	if err := c.Some(&messageIds, query); err != nil {
		return nil, err
	}

	if messageIds == nil {
		return make([]int64, 0), nil
	}

	return messageIds, nil
}

// BySlug fetchs channel message by its slug
// checks if message is in the channel or not
func (c *ChannelMessage) BySlug(query *request.Query) error {
	if query.Slug == "" {
		return errors.New("slug is not set")
	}

	// fetch message itself
	q := &bongo.Query{
		Selector: map[string]interface{}{
			"slug": query.Slug,
		},
	}

	if err := c.One(q); err != nil {
		return err
	}

	// fetch channel by group name
	query.Name = query.GroupName
	query.Type = Channel_TYPE_GROUP
	ch := NewChannel()
	channel, err := ch.ByName(query)
	if err != nil {
		return err
	}

	if channel.Id == 0 {
		return errors.New("channel is not found")
	}

	// check if message is in the channel
	cml := NewChannelMessageList()
	res, err := cml.IsInChannel(c.Id, channel.Id)
	if err != nil {
		return err
	}

	// if message is not in the channel
	if !res {
		return bongo.RecordNotFound
	}

	return nil
}
<|endoftext|>


<commit_before>package golog

import (
	"io"
	"os"
	"sync"
	"time"

	"github.com/chapsuk/golog/syslog"
)

// SyslogWriter write logs to syslog
type SyslogWriter struct {
	Writer  io.WriteCloser
	timeout time.Duration
	mu      sync.Mutex
	done    chan struct{}
}

// NewSyslogWriter return new SyslogWriter instance with concurrent writer to syslog
func NewSyslogWriter(network, addr, tag string, timeout time.Duration) *SyslogWriter {
	s := &SyslogWriter{
		timeout: timeout,
		done:    make(chan struct{}),
	}
	w, err := syslog.Dial(network, addr, syslog.LOG_USER, tag)
	if err != nil {
		s.Writer = os.Stdout
		std.Errorf("error connecting to syslog: %s", err.Error())

		go func() {
			t := time.NewTicker(s.timeout)
			for {
				select {
				case <-t.C:
					w, err := syslog.Dial(network, addr, syslog.LOG_USER, tag)
					if err != nil {
						std.Errorf("error connecting to syslog: %s", err.Error())
						continue
					}

					t.Stop()
					s.mu.Lock()
					s.Writer = w
					s.mu.Unlock()
					break
				case <-s.done:
					break
				}
			}
		}()
		return s
	}

	s.Writer = w
	return s
}

// Write writes data to syslog
func (w *SyslogWriter) Write(p []byte) (int, error) {
	w.mu.Lock()
	defer w.mu.Unlock()
	if sl, ok := w.Writer.(*syslog.Writer); ok {
		err := sl.SetWriteDeadLine(time.Now().Add(w.timeout))
		if err != nil {
			return 0, err
		}
	}
	return w.Writer.Write(p)
}

// Close close connection
func (w *SyslogWriter) Close() {
	w.done <- struct{}{}
	w.Writer.Close()
}
<commit_msg>Fixed break from syslog connection loop (#3)<commit_after>package golog

import (
	"io"
	"os"
	"sync"
	"time"

	"github.com/chapsuk/golog/syslog"
)

// SyslogWriter write logs to syslog
type SyslogWriter struct {
	Writer  io.WriteCloser
	timeout time.Duration
	mu      sync.Mutex
	done    chan struct{}
}

// NewSyslogWriter return new SyslogWriter instance with concurrent writer to syslog
func NewSyslogWriter(network, addr, tag string, timeout time.Duration) *SyslogWriter {
	s := &SyslogWriter{
		timeout: timeout,
		done:    make(chan struct{}),
	}
	w, err := syslog.Dial(network, addr, syslog.LOG_USER, tag)
	if err != nil {
		s.Writer = os.Stdout
		std.Errorf("error connecting to syslog: %s", err.Error())

		go func() {
			t := time.NewTicker(s.timeout)
			defer t.Stop()

			for {
				select {
				case <-t.C:
					w, err := syslog.Dial(network, addr, syslog.LOG_USER, tag)
					if err != nil {
						std.Errorf("error connecting to syslog: %s", err.Error())
						continue
					}

					s.mu.Lock()
					s.Writer = w
					s.mu.Unlock()
					return
				case <-s.done:
					return
				}
			}
		}()
		return s
	}

	s.Writer = w
	return s
}

// Write writes data to syslog
func (w *SyslogWriter) Write(p []byte) (int, error) {
	w.mu.Lock()
	defer w.mu.Unlock()
	if sl, ok := w.Writer.(*syslog.Writer); ok {
		err := sl.SetWriteDeadLine(time.Now().Add(w.timeout))
		if err != nil {
			return 0, err
		}
	}
	return w.Writer.Write(p)
}

// Close close connection
func (w *SyslogWriter) Close() {
	w.done <- struct{}{}
	w.Writer.Close()
}
<|endoftext|>


<commit_before>package keepalivego

import (
	"fmt"
	"net"
	"syscall"

	"github.com/hnakamur/ltsvlog"
	"github.com/mqliang/libipvs"
)

type LVS struct {
	ipvs libipvs.IPVSHandle
}

type Config struct {
	LogFile        string       "yaml:`logfile`"
	EnableDebugLog bool         "yaml:`enable_debug_log`"
	Vrrp           []ConfigVrrp "yaml:`vrrp`"
	Lvs            []ConfigLvs  "yaml:`lvs`"
}

type ConfigVrrp struct {
	Vrid     int    "yaml:`vrid`"
	Priority int    "yaml:`priority`"
	Address  string "yaml:`address`"
}

type ConfigLvs struct {
	Name     string         "yaml:`name`"
	Port     uint16         "yaml:`port`"
	Address  string         "yaml:`address`"
	Schedule string         "yaml:`schedule`"
	Type     string         "yaml:`type`"
	Servers  []ConfigServer "yaml:`servers`"
}

type ConfigServer struct {
	Port    uint16 "yaml:`port`"
	Address string "yaml:`address`"
	Weight  uint32 "yaml:`weight`"
}

func New() (*LVS, error) {
	ipvs, err := libipvs.New()
	if err != nil {
		return nil, ltsvlog.WrapErr(err, func(err error) error {
			return fmt.Errorf("failed to create libipvs handler, err=%v", err)
		}).Stack("")
	}

	return &LVS{ipvs: ipvs}, nil
}

func (l *LVS) ReloadConfig(config *Config) error {
	ipvsServices, err := l.ipvs.ListServices()
	if err != nil {
		return ltsvlog.WrapErr(err, func(err error) error {
			return fmt.Errorf("failed to list ipvs services, err=%v", err)
		}).Stack("")
	}

	// 不要な設定を削除
	for _, ipvsService := range ipvsServices {
		var serviceConf ConfigLvs
		exist := false
		for _, serviceConf := range config.Lvs {
			if ipvsService.Address.Equal(net.ParseIP(serviceConf.Address)) {
				exist = true
				break
			}
		}
		if exist {
			ipvsDests, err := l.ipvs.ListDestinations(ipvsService)
			if err != nil {
				return ltsvlog.WrapErr(err, func(err error) error {
					return fmt.Errorf("failed to list ipvs destinations, err=%v", err)
				}).Stack("")
			}
			for _, ipvsDest := range ipvsDests {
				exist = false
				for _, server := range serviceConf.Servers {
					if ipvsDest.Address.Equal(net.ParseIP(server.Address)) {
						exist = true
						break
					}
				}
				if !exist {
					err := l.ipvs.DelDestination(ipvsService, ipvsDest)
					if err != nil {
						return ltsvlog.WrapErr(err, func(err error) error {
							return fmt.Errorf("faild delete ipvs destination, err=%v", err)
						}).Stack("")
					}
				}
			}
		} else {
			err := l.ipvs.DelService(ipvsService)
			if err != nil {
				return ltsvlog.WrapErr(err, func(err error) error {
					return fmt.Errorf("faild delete ipvs service, err=%s", err)
				}).Stringer("serviceAddress", ipvsService.Address).Stack("")
			}
			ltsvlog.Logger.Info().String("msg", "deleted ipvs service").Stringer("serviceAddress", ipvsService.Address).Log()
		}
	}

	// 設定追加 更新
	for _, serviceConf := range config.Lvs {
		ipAddr := net.ParseIP(serviceConf.Address)
		var ipvsService *libipvs.Service
		exist := false
		for _, ipvsService = range ipvsServices {
			if ipvsService.Address.Equal(ipAddr) {
				exist = true
				break
			}
		}
		family := libipvs.AddressFamily(ipAddressFamily(ipAddr))
		service := libipvs.Service{
			Address:       ipAddr,
			AddressFamily: family,
			Protocol:      libipvs.Protocol(syscall.IPPROTO_TCP),
			Port:          serviceConf.Port,
			SchedName:     serviceConf.Schedule,
		}
		if !exist {
			if err := l.ipvs.NewService(&service); err != nil {
				return ltsvlog.WrapErr(err, func(err error) error {
					return fmt.Errorf("faild create ipvs service, err=%s", err)
				}).String("address", serviceConf.Address).
					Uint16("port", serviceConf.Port).
					String("schedule", serviceConf.Schedule).Stack("")
			}
			ipvsService = &service
		} else {
			if err := l.ipvs.UpdateService(&service); err != nil {
				return ltsvlog.WrapErr(err, func(err error) error {
					return fmt.Errorf("faild update ipvs service, err=%s", err)
				}).String("address", serviceConf.Address).
					Uint16("port", serviceConf.Port).
					String("schedule", serviceConf.Schedule).Stack("")
			}
		}

		ipvsDests, err := l.ipvs.ListDestinations(ipvsService)
		if err != nil {
			return ltsvlog.WrapErr(err, func(err error) error {
				return fmt.Errorf("failed to list ipvs destinations, err=%v", err)
			}).Stack("")
		}

		for _, server := range serviceConf.Servers {
			ipAddr := net.ParseIP(server.Address)
			exist = false
			for _, ipvsDest := range ipvsDests {
				if ipvsDest.Address.Equal(ipAddr) {
					exist = true
					break
				}
			}
			var fwd libipvs.FwdMethod
			switch serviceConf.Type {
			case "dr":
				fwd = libipvs.IP_VS_CONN_F_DROUTE
			case "nat":
				fallthrough
			default:
				fwd = libipvs.IP_VS_CONN_F_MASQ
			}
			family := libipvs.AddressFamily(ipAddressFamily(ipAddr))
			dest := libipvs.Destination{
				Address:       ipAddr,
				AddressFamily: family,
				Port:          server.Port,
				FwdMethod:     fwd,
				Weight:        server.Weight,
			}
			if exist {
				err := l.ipvs.UpdateDestination(ipvsService, &dest)
				if err != nil {
					return ltsvlog.WrapErr(err, func(err error) error {
						return fmt.Errorf("faild create ipvs destination, err=%s", err)
					}).String("address", server.Address).
						Uint16("port", server.Port).
						String("fwdMethod", serviceConf.Type).
						Uint32("weight", server.Weight).Stack("")
				}
			} else {
				err := l.ipvs.NewDestination(ipvsService, &dest)
				if err != nil {
					return ltsvlog.WrapErr(err, func(err error) error {
						return fmt.Errorf("faild create ipvs destination, err=%s", err)
					}).String("address", server.Address).
						Uint16("port", server.Port).
						String("fwdMethod", serviceConf.Type).
						Uint32("weight", server.Weight).Stack("")
				}
			}
		}
	}
	return nil
}

func ipAddressFamily(ip net.IP) int {
	if ip.To4() != nil {
		return syscall.AF_INET
	} else {
		return syscall.AF_INET6
	}
}
<commit_msg>Change abbreviations to upper case<commit_after>package keepalivego

import (
	"fmt"
	"net"
	"syscall"

	"github.com/hnakamur/ltsvlog"
	"github.com/mqliang/libipvs"
)

type LVS struct {
	ipvs libipvs.IPVSHandle
}

type Config struct {
	LogFile        string       "yaml:`logfile`"
	EnableDebugLog bool         "yaml:`enable_debug_log`"
	VRRP           []ConfigVRRP "yaml:`vrrp`"
	LVS            []ConfigLVS  "yaml:`lvs`"
}

type ConfigVRRP struct {
	VRID     int    "yaml:`vrid`"
	Priority int    "yaml:`priority`"
	Address  string "yaml:`address`"
}

type ConfigLVS struct {
	Name     string         "yaml:`name`"
	Port     uint16         "yaml:`port`"
	Address  string         "yaml:`address`"
	Schedule string         "yaml:`schedule`"
	Type     string         "yaml:`type`"
	Servers  []ConfigServer "yaml:`servers`"
}

type ConfigServer struct {
	Port    uint16 "yaml:`port`"
	Address string "yaml:`address`"
	Weight  uint32 "yaml:`weight`"
}

func New() (*LVS, error) {
	ipvs, err := libipvs.New()
	if err != nil {
		return nil, ltsvlog.WrapErr(err, func(err error) error {
			return fmt.Errorf("failed to create libipvs handler, err=%v", err)
		}).Stack("")
	}

	return &LVS{ipvs: ipvs}, nil
}

func (l *LVS) ReloadConfig(config *Config) error {
	ipvsServices, err := l.ipvs.ListServices()
	if err != nil {
		return ltsvlog.WrapErr(err, func(err error) error {
			return fmt.Errorf("failed to list ipvs services, err=%v", err)
		}).Stack("")
	}

	// 不要な設定を削除
	for _, ipvsService := range ipvsServices {
		var serviceConf ConfigLVS
		exist := false
		for _, serviceConf := range config.LVS {
			if ipvsService.Address.Equal(net.ParseIP(serviceConf.Address)) {
				exist = true
				break
			}
		}
		if exist {
			ipvsDests, err := l.ipvs.ListDestinations(ipvsService)
			if err != nil {
				return ltsvlog.WrapErr(err, func(err error) error {
					return fmt.Errorf("failed to list ipvs destinations, err=%v", err)
				}).Stack("")
			}
			for _, ipvsDest := range ipvsDests {
				exist = false
				for _, server := range serviceConf.Servers {
					if ipvsDest.Address.Equal(net.ParseIP(server.Address)) {
						exist = true
						break
					}
				}
				if !exist {
					err := l.ipvs.DelDestination(ipvsService, ipvsDest)
					if err != nil {
						return ltsvlog.WrapErr(err, func(err error) error {
							return fmt.Errorf("faild delete ipvs destination, err=%v", err)
						}).Stack("")
					}
				}
			}
		} else {
			err := l.ipvs.DelService(ipvsService)
			if err != nil {
				return ltsvlog.WrapErr(err, func(err error) error {
					return fmt.Errorf("faild delete ipvs service, err=%s", err)
				}).Stringer("serviceAddress", ipvsService.Address).Stack("")
			}
			ltsvlog.Logger.Info().String("msg", "deleted ipvs service").Stringer("serviceAddress", ipvsService.Address).Log()
		}
	}

	// 設定追加 更新
	for _, serviceConf := range config.LVS {
		ipAddr := net.ParseIP(serviceConf.Address)
		var ipvsService *libipvs.Service
		exist := false
		for _, ipvsService = range ipvsServices {
			if ipvsService.Address.Equal(ipAddr) {
				exist = true
				break
			}
		}
		family := libipvs.AddressFamily(ipAddressFamily(ipAddr))
		service := libipvs.Service{
			Address:       ipAddr,
			AddressFamily: family,
			Protocol:      libipvs.Protocol(syscall.IPPROTO_TCP),
			Port:          serviceConf.Port,
			SchedName:     serviceConf.Schedule,
		}
		if !exist {
			if err := l.ipvs.NewService(&service); err != nil {
				return ltsvlog.WrapErr(err, func(err error) error {
					return fmt.Errorf("faild create ipvs service, err=%s", err)
				}).String("address", serviceConf.Address).
					Uint16("port", serviceConf.Port).
					String("schedule", serviceConf.Schedule).Stack("")
			}
			ipvsService = &service
		} else {
			if err := l.ipvs.UpdateService(&service); err != nil {
				return ltsvlog.WrapErr(err, func(err error) error {
					return fmt.Errorf("faild update ipvs service, err=%s", err)
				}).String("address", serviceConf.Address).
					Uint16("port", serviceConf.Port).
					String("schedule", serviceConf.Schedule).Stack("")
			}
		}

		ipvsDests, err := l.ipvs.ListDestinations(ipvsService)
		if err != nil {
			return ltsvlog.WrapErr(err, func(err error) error {
				return fmt.Errorf("failed to list ipvs destinations, err=%v", err)
			}).Stack("")
		}

		for _, server := range serviceConf.Servers {
			ipAddr := net.ParseIP(server.Address)
			exist = false
			for _, ipvsDest := range ipvsDests {
				if ipvsDest.Address.Equal(ipAddr) {
					exist = true
					break
				}
			}
			var fwd libipvs.FwdMethod
			switch serviceConf.Type {
			case "dr":
				fwd = libipvs.IP_VS_CONN_F_DROUTE
			case "nat":
				fallthrough
			default:
				fwd = libipvs.IP_VS_CONN_F_MASQ
			}
			family := libipvs.AddressFamily(ipAddressFamily(ipAddr))
			dest := libipvs.Destination{
				Address:       ipAddr,
				AddressFamily: family,
				Port:          server.Port,
				FwdMethod:     fwd,
				Weight:        server.Weight,
			}
			if exist {
				err := l.ipvs.UpdateDestination(ipvsService, &dest)
				if err != nil {
					return ltsvlog.WrapErr(err, func(err error) error {
						return fmt.Errorf("faild create ipvs destination, err=%s", err)
					}).String("address", server.Address).
						Uint16("port", server.Port).
						String("fwdMethod", serviceConf.Type).
						Uint32("weight", server.Weight).Stack("")
				}
			} else {
				err := l.ipvs.NewDestination(ipvsService, &dest)
				if err != nil {
					return ltsvlog.WrapErr(err, func(err error) error {
						return fmt.Errorf("faild create ipvs destination, err=%s", err)
					}).String("address", server.Address).
						Uint16("port", server.Port).
						String("fwdMethod", serviceConf.Type).
						Uint32("weight", server.Weight).Stack("")
				}
			}
		}
	}
	return nil
}

func ipAddressFamily(ip net.IP) int {
	if ip.To4() != nil {
		return syscall.AF_INET
	} else {
		return syscall.AF_INET6
	}
}
<|endoftext|>