<commit_before>package minimalirc

import (
	"bufio"
	"crypto/tls"
	"errors"
	"fmt"
	"log"
	"math/rand"
	"net"
	"net/textproto"
	"strings"
	"time"
	"unicode"
)

/*
 * minimalirc.go
 * small library to connect to an IRC server
 * by J. Stuart McMurray
 * created 20141130
 * last modified 20141201
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2014 J. Stuart McMurray
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/* IRC represents a connection to an IRC server via OpenSSL */
type IRC struct {
	r       *textproto.Reader /* Reads messages from server */
	w       *textproto.Writer /* Writes messages to server */
	C       <-chan string     /* Messages from R are sent here */
	c       chan string       /* Sendable, closable C */
	E       <-chan error      /* Receives an error before close(C) */
	e       chan error        /* Sendable E */
	S       net.Conn          /* Represents the connection to the server */
	Msglen  int               /* Size of an IRC message */
	Default string            /* Default target for privmsgs */
	rng     *rand.Rand        /* Random number generator */
	snick   string            /* The server's idea of our nick */

	/* Configs and defauls.  These may be changed at any time. */
	Host          string /* Host to which to connect */
	Port          uint16 /* Port to which to connect */
	Ssl           bool   /* True to use SSL/TLS */
	Hostname      string /* Hostname to verify on server's certificate */
	Nick          string /* For NICK */
	Username      string /* For USER */
	Realname      string /* For USER */
	IdNick        string /* To auth to NickServ */
	IdPass        string /* To auth to NickServ */
	Channel       string /* For JOIN */
	Chanpass      string /* For JOIN */
	Txp           string /* Prefix for logging sent messages */
	Rxp           string /* Prefix for logging received messages */
	Pongs         bool   /* Automatic ping responses */
	RandomNumbers bool   /* Append random numbers to the nick */
	QuitMessage   string /* Message to send when the client QUITs */
}

// New allocates, initializes, and returns a pointer to a new IRC struct.  hostname will be ignored if ssl is false, or assumed to be the same as host if it is the empty string and ssl is true.
func New(host string, port uint16, ssl bool, hostname string,
	nick, username, realname string) *IRC {
	/* Struct to return */
	i := &IRC{}
	/* Random number generator */
	i.rng = rand.New(rand.NewSource(time.Now().Unix()))
	/* Default max message length */
	i.Msglen = 467
	/* I/O channels */
	i.c = make(chan string)
	i.C = i.c
	i.e = make(chan error, 1)
	i.E = i.e
	i.Host = host
	i.Port = port
	i.Ssl = ssl
	if i.Ssl && "" == hostname {
		hostname = host
	}
	i.Hostname = hostname
	i.Nick = nick
	i.Username = username
	i.Realname = realname

	return i
}

// Connect connects to the server, and calls Handshake().  After connect returns, messages sent by the IRC server will be available on i.C.  If i.Rxp is set, received messages from the server will be logged via log.Printf prefixed by i.Rxp, separated by a space.  If an error is encountered reading messages from the IRC server, i.C will be closed and the error will be sent on i.E.  i.S represents the connection to the server.
func (i *IRC) Connect() error {
	/* Dial the server */
	h := net.JoinHostPort(i.Host, fmt.Sprintf("%v", i.Port))
	if i.Ssl { /* SSL requested */
		var err error
		i.S, err = tls.Dial("tcp", h,
			&tls.Config{ServerName: i.Hostname})
		if nil != err {
			return errors.New(fmt.Sprintf("unable to make ssl "+
				"connection to %v: %v", h, err))
		}
	} else { /* Plaintext connection */
		var err error
		i.S, err = net.Dial("tcp", h)
		if nil != err {
			return errors.New(fmt.Sprintf("unable to make "+
				"plaintext connection to %v: %v", h, err))
		}
	}

	/* Make a reader and a writer */
	i.r = textproto.NewReader(bufio.NewReader(i.S))
	i.w = textproto.NewWriter(bufio.NewWriter(i.S))

	/* Send nick and user */
	if err := i.Handshake(); nil != err {
		return errors.New(fmt.Sprintf("unable to handshake: %v", err))
	}

	/* Start reads from server into channel */
	go func() {
		for {
			/* Get a line from the reader */
			line, err := i.r.ReadLine()
			/* Close the channel on error */
			if nil != err {
				i.e <- err
				close(i.c)
			}
			/* Log the line if needed */
			if "" != i.Rxp {
				log.Printf("%v %v", i.Rxp, line)
			}
			/* Handle pings if desired */
			if i.Pongs && strings.HasPrefix(strings.ToLower(line),
				"ping ") {
				/* Try to send pong */
				err := i.PrintfLine("PONG %v",
					strings.SplitN(line, " ", 2)[1])
				/* A send error is as bad as a read error */
				if nil != err {
					i.e <- err
					close(i.c)
				}
			}
			/* Maybe get a nick */
			parts := strings.SplitN(line, " ", 4)
			/* If the 2nd bit is a 3-digit number, the 3rd bit is
			our nick */
			if 4 == len(parts) {
				n := []rune(parts[1])
				if 3 == len(n) &&
					unicode.IsNumber(n[0]) &&
					unicode.IsDigit(n[1]) &&
					unicode.IsDigit(n[2]) {
					i.snick = parts[2]
				}
			}

			/* Send out the line */
			i.c <- line
		}
	}()
	return nil
}

// ID sets the nick and user from the values in i, and sends a NICK command without any parameters (to get an easy-to-parse response with the nick as the server knows it).  If i.Nick, i.Username or i.Realname are the empty string, this is a no-op.
func (i *IRC) ID() error {
	if "" == i.Nick || "" == i.Username || "" == i.Realname {
		return nil
	}
	/* Add some numbers to the nick */
	nick := i.Nick
	if i.RandomNumbers {
		nick = fmt.Sprintf("%v-%v", nick, i.rng.Int63())
	}
	/* Iterate over the commands to send */
	for _, line := range []string{
		fmt.Sprintf("NICK :%v", nick),
		fmt.Sprintf("USER %v x x :%v", i.Username, i.Realname),
		"NICK",
	} {
		/* Try to send the line */
		if err := i.PrintfLine(line); nil != err {
			return errors.New(fmt.Sprintf("error sending ID "+
				"line %v: %v", line, err))
		}
	}
	return nil
}

// Auth authenticates to NickServ with the values in i.  If either i.IdNick or i.IdPass are the empty string, this is a no-op.
func (i *IRC) Auth() error {
	/* Don't auth with blank creds */
	if "" == i.IdNick || "" == i.IdPass {
		return nil
	}
	l := fmt.Sprintf("PRIVMSG NickServ :identify %v %v", i.IdNick,
		i.IdPass)
	if err := i.PrintfLine(l); nil != err {
		return errors.New(fmt.Sprintf("error authenticating to "+
			"services: %v", err))
	}
	return nil
}

// Join joins the channel with the optional password (which may be the empty string).  If the channel is the empty string, the value from i.Channel and i.Chanpass will be used.  If channel and i.Channel are both the empty string, this is a no-op.
func (i *IRC) Join(channel, pass string) error {
	/* If not specified, try the channel in i */
	if "" == channel {
		channel = i.Channel
		pass = i.Chanpass
	}
	/* If still no channel, no-op */
	if "" == channel {
		return nil
	}
	l := fmt.Sprintf("JOIN %v %v", channel, pass)
	if err := i.PrintfLine(l); nil != err {
		return errors.New(fmt.Sprintf("error joining %v: %v",
			channel, err))
	}
	return nil
}

// Handshake is a shorthand for ID, Auth, and Join, in that order, using the values in i.
func (i *IRC) Handshake() error {
	/* Set nick and user */
	if err := i.ID(); nil != err {
		return errors.New(fmt.Sprintf("handshake error (ID): %v", err))
	}
	/* Auth to services */
	if err := i.Auth(); err != nil {
		return errors.New(fmt.Sprintf("handshake error (Auth): %v",
			err))
	}
	/* Join the channel */
	if err := i.Join("", ""); err != nil {
		return errors.New(fmt.Sprintf("handshake error (Join): %v",
			err))
	}
	return nil
}

// PrintfLine sends the formatted string to the IRC server.  The message should be a raw IRC protocol message (like WHOIS or CAP).  It is not wrapped in PRIVMSG or anything else.  For PRIVMSGs, see Privmsg  .If i.Txp is not the empty string, successfully sent lines will be logged via log.Printf() prefixed by i.Txp, separated by a space.  Note that all the functions used to send protocol messages use PrintfLine.
func (i *IRC) PrintfLine(f string, args ...interface{}) error {
	/* Form the line into a string */
	line := fmt.Sprintf(f, args...)
	/* Try to send the line */
	if err := i.w.PrintfLine(line); err != nil {
		return err
	}
	/* Log if desired */
	if "" != i.Txp {
		log.Printf("%v %v", i.Txp, line)
	}
	return nil
}

// Target returns a target suitable for use in Privmsg, or "" if there is none.
func (i *IRC) target(target string) string {
	/* Use the default target if none was given */
	if "" == target {
		target = i.Default
	}
	/* If no default, use channel */
	if "" == target {
		target = i.Channel
	}
	/* Nop if no default target */
	if "" == target {
		return ""
	}
	return target
}

// Privmsg sends a PRIVMSG to the target, which may be a nick or a channel.  If the target is an empty string, the message will be sent to i.Target, unless that is also an empty string, in which case nothing is sent.
func (i *IRC) Privmsg(msg, target string) error {
	/* Get the target */
	t := i.target(target)
	if "" == t {
		return nil
	}
	/* Send the message */
	return i.PrintfLine("PRIVMSG %v :%v", t, msg)
}

// PrivmsgSize returns the length of the message that can be shoved into a PRIVMSG to the target.  i.Msglen may be changed to override the default size of an IRC message (467 bytes, determined experimentally on freenode, 510 should be it, though).  See Privmsg for the meaning of target.
func (i *IRC) PrivmsgSize(target string) int {
	/* Get the target */
	t := i.target(target)
	if "" == t {
		return -1
	}
	return i.Msglen - len([]byte(fmt.Sprintf("PRIVMSG %v :", t)))
}

// Nick returns a guess as to what the server thinks the nick is.  This is handy for servers that truncate nicks when RandomNumbers is true.  This is, however, only a guess (albiet a good one).  It should be called after setting the nick with Nick() or Handshake().  Note this is based on passive inspection of received messagess, which requires reading due to the read channel being unbuffered. */
func (i *IRC) SNick() string {
	return i.snick
}

// Quit sends a QUIT command to the IRC server, with the optional msg as the quit message and closes the connection if the send succeeds.  If msg is the empty string, i.QuitMessage will be used, unless it's also the empty string, in which case no message is sent with the QUIT command.
func (i *IRC) Quit(msg string) error {
	/* Use the stored message if msg is empty */
	if "" == msg && "" != i.QuitMessage {
		msg = i.QuitMessage
	}
	/* Make the message protocolish */
	if "" != msg {
		msg = " :" + msg
	}
	/* Send the quit message */
	if err := i.PrintfLine("QUIT%v", msg); nil != err {
		return err
	}
	/* Close the connection */
	if err := i.S.Close(); nil != err {
		return err
	}

	return nil
}
<commit_msg>Reordered struct members<commit_after>package minimalirc

import (
	"bufio"
	"crypto/tls"
	"errors"
	"fmt"
	"log"
	"math/rand"
	"net"
	"net/textproto"
	"strings"
	"time"
	"unicode"
)

/*
 * minimalirc.go
 * small library to connect to an IRC server
 * by J. Stuart McMurray
 * created 20141130
 * last modified 20141201
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2014 J. Stuart McMurray
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/* IRC represents a connection to an IRC server via OpenSSL */
type IRC struct {
	r       *textproto.Reader /* Reads messages from server */
	w       *textproto.Writer /* Writes messages to server */
	C       <-chan string     /* Messages from the server are sent here */
	E       <-chan error      /* Receives an error before close(C) */
	S       net.Conn          /* Represents the connection to the server */
	c       chan string       /* Sendable, closable C */
	e       chan error        /* Sendable E */
	Msglen  int               /* Size of an IRC message */
	Default string            /* Default target for privmsgs */
	rng     *rand.Rand        /* Random number generator */
	snick   string            /* The server's idea of our nick */

	/* Configs and defauls.  These may be changed at any time. */
	Host          string /* Host to which to connect */
	Port          uint16 /* Port to which to connect */
	Ssl           bool   /* True to use SSL/TLS */
	Hostname      string /* Hostname to verify on server's certificate */
	Nick          string /* For NICK */
	Username      string /* For USER */
	Realname      string /* For USER */
	IdNick        string /* To auth to NickServ */
	IdPass        string /* To auth to NickServ */
	Channel       string /* For JOIN */
	Chanpass      string /* For JOIN */
	Txp           string /* Prefix for logging sent messages */
	Rxp           string /* Prefix for logging received messages */
	Pongs         bool   /* Automatic ping responses */
	RandomNumbers bool   /* Append random numbers to the nick */
	QuitMessage   string /* Message to send when the client QUITs */
}

// New allocates, initializes, and returns a pointer to a new IRC struct.  hostname will be ignored if ssl is false, or assumed to be the same as host if it is the empty string and ssl is true.
func New(host string, port uint16, ssl bool, hostname string,
	nick, username, realname string) *IRC {
	/* Struct to return */
	i := &IRC{}
	/* Random number generator */
	i.rng = rand.New(rand.NewSource(time.Now().Unix()))
	/* Default max message length */
	i.Msglen = 467
	/* I/O channels */
	i.c = make(chan string)
	i.C = i.c
	i.e = make(chan error, 1)
	i.E = i.e
	i.Host = host
	i.Port = port
	i.Ssl = ssl
	if i.Ssl && "" == hostname {
		hostname = host
	}
	i.Hostname = hostname
	i.Nick = nick
	i.Username = username
	i.Realname = realname

	return i
}

// Connect connects to the server, and calls Handshake().  After connect returns, messages sent by the IRC server will be available on i.C.  If i.Rxp is set, received messages from the server will be logged via log.Printf prefixed by i.Rxp, separated by a space.  If an error is encountered reading messages from the IRC server, i.C will be closed and the error will be sent on i.E.  i.S represents the connection to the server.
func (i *IRC) Connect() error {
	/* Dial the server */
	h := net.JoinHostPort(i.Host, fmt.Sprintf("%v", i.Port))
	if i.Ssl { /* SSL requested */
		var err error
		i.S, err = tls.Dial("tcp", h,
			&tls.Config{ServerName: i.Hostname})
		if nil != err {
			return errors.New(fmt.Sprintf("unable to make ssl "+
				"connection to %v: %v", h, err))
		}
	} else { /* Plaintext connection */
		var err error
		i.S, err = net.Dial("tcp", h)
		if nil != err {
			return errors.New(fmt.Sprintf("unable to make "+
				"plaintext connection to %v: %v", h, err))
		}
	}

	/* Make a reader and a writer */
	i.r = textproto.NewReader(bufio.NewReader(i.S))
	i.w = textproto.NewWriter(bufio.NewWriter(i.S))

	/* Send nick and user */
	if err := i.Handshake(); nil != err {
		return errors.New(fmt.Sprintf("unable to handshake: %v", err))
	}

	/* Start reads from server into channel */
	go func() {
		for {
			/* Get a line from the reader */
			line, err := i.r.ReadLine()
			/* Close the channel on error */
			if nil != err {
				i.e <- err
				close(i.c)
			}
			/* Log the line if needed */
			if "" != i.Rxp {
				log.Printf("%v %v", i.Rxp, line)
			}
			/* Handle pings if desired */
			if i.Pongs && strings.HasPrefix(strings.ToLower(line),
				"ping ") {
				/* Try to send pong */
				err := i.PrintfLine("PONG %v",
					strings.SplitN(line, " ", 2)[1])
				/* A send error is as bad as a read error */
				if nil != err {
					i.e <- err
					close(i.c)
				}
			}
			/* Maybe get a nick */
			parts := strings.SplitN(line, " ", 4)
			/* If the 2nd bit is a 3-digit number, the 3rd bit is
			our nick */
			if 4 == len(parts) {
				n := []rune(parts[1])
				if 3 == len(n) &&
					unicode.IsNumber(n[0]) &&
					unicode.IsDigit(n[1]) &&
					unicode.IsDigit(n[2]) {
					i.snick = parts[2]
				}
			}

			/* Send out the line */
			i.c <- line
		}
	}()
	return nil
}

// ID sets the nick and user from the values in i, and sends a NICK command without any parameters (to get an easy-to-parse response with the nick as the server knows it).  If i.Nick, i.Username or i.Realname are the empty string, this is a no-op.
func (i *IRC) ID() error {
	if "" == i.Nick || "" == i.Username || "" == i.Realname {
		return nil
	}
	/* Add some numbers to the nick */
	nick := i.Nick
	if i.RandomNumbers {
		nick = fmt.Sprintf("%v-%v", nick, i.rng.Int63())
	}
	/* Iterate over the commands to send */
	for _, line := range []string{
		fmt.Sprintf("NICK :%v", nick),
		fmt.Sprintf("USER %v x x :%v", i.Username, i.Realname),
		"NICK",
	} {
		/* Try to send the line */
		if err := i.PrintfLine(line); nil != err {
			return errors.New(fmt.Sprintf("error sending ID "+
				"line %v: %v", line, err))
		}
	}
	return nil
}

// Auth authenticates to NickServ with the values in i.  If either i.IdNick or i.IdPass are the empty string, this is a no-op.
func (i *IRC) Auth() error {
	/* Don't auth with blank creds */
	if "" == i.IdNick || "" == i.IdPass {
		return nil
	}
	l := fmt.Sprintf("PRIVMSG NickServ :identify %v %v", i.IdNick,
		i.IdPass)
	if err := i.PrintfLine(l); nil != err {
		return errors.New(fmt.Sprintf("error authenticating to "+
			"services: %v", err))
	}
	return nil
}

// Join joins the channel with the optional password (which may be the empty string).  If the channel is the empty string, the value from i.Channel and i.Chanpass will be used.  If channel and i.Channel are both the empty string, this is a no-op.
func (i *IRC) Join(channel, pass string) error {
	/* If not specified, try the channel in i */
	if "" == channel {
		channel = i.Channel
		pass = i.Chanpass
	}
	/* If still no channel, no-op */
	if "" == channel {
		return nil
	}
	l := fmt.Sprintf("JOIN %v %v", channel, pass)
	if err := i.PrintfLine(l); nil != err {
		return errors.New(fmt.Sprintf("error joining %v: %v",
			channel, err))
	}
	return nil
}

// Handshake is a shorthand for ID, Auth, and Join, in that order, using the values in i.
func (i *IRC) Handshake() error {
	/* Set nick and user */
	if err := i.ID(); nil != err {
		return errors.New(fmt.Sprintf("handshake error (ID): %v", err))
	}
	/* Auth to services */
	if err := i.Auth(); err != nil {
		return errors.New(fmt.Sprintf("handshake error (Auth): %v",
			err))
	}
	/* Join the channel */
	if err := i.Join("", ""); err != nil {
		return errors.New(fmt.Sprintf("handshake error (Join): %v",
			err))
	}
	return nil
}

// PrintfLine sends the formatted string to the IRC server.  The message should be a raw IRC protocol message (like WHOIS or CAP).  It is not wrapped in PRIVMSG or anything else.  For PRIVMSGs, see Privmsg  .If i.Txp is not the empty string, successfully sent lines will be logged via log.Printf() prefixed by i.Txp, separated by a space.  Note that all the functions used to send protocol messages use PrintfLine.
func (i *IRC) PrintfLine(f string, args ...interface{}) error {
	/* Form the line into a string */
	line := fmt.Sprintf(f, args...)
	/* Try to send the line */
	if err := i.w.PrintfLine(line); err != nil {
		return err
	}
	/* Log if desired */
	if "" != i.Txp {
		log.Printf("%v %v", i.Txp, line)
	}
	return nil
}

// Target returns a target suitable for use in Privmsg, or "" if there is none.
func (i *IRC) target(target string) string {
	/* Use the default target if none was given */
	if "" == target {
		target = i.Default
	}
	/* If no default, use channel */
	if "" == target {
		target = i.Channel
	}
	/* Nop if no default target */
	if "" == target {
		return ""
	}
	return target
}

// Privmsg sends a PRIVMSG to the target, which may be a nick or a channel.  If the target is an empty string, the message will be sent to i.Target, unless that is also an empty string, in which case nothing is sent.
func (i *IRC) Privmsg(msg, target string) error {
	/* Get the target */
	t := i.target(target)
	if "" == t {
		return nil
	}
	/* Send the message */
	return i.PrintfLine("PRIVMSG %v :%v", t, msg)
}

// PrivmsgSize returns the length of the message that can be shoved into a PRIVMSG to the target.  i.Msglen may be changed to override the default size of an IRC message (467 bytes, determined experimentally on freenode, 510 should be it, though).  See Privmsg for the meaning of target.
func (i *IRC) PrivmsgSize(target string) int {
	/* Get the target */
	t := i.target(target)
	if "" == t {
		return -1
	}
	return i.Msglen - len([]byte(fmt.Sprintf("PRIVMSG %v :", t)))
}

// Nick returns a guess as to what the server thinks the nick is.  This is handy for servers that truncate nicks when RandomNumbers is true.  This is, however, only a guess (albiet a good one).  It should be called after setting the nick with Nick() or Handshake().  Note this is based on passive inspection of received messagess, which requires reading due to the read channel being unbuffered. */
func (i *IRC) SNick() string {
	return i.snick
}

// Quit sends a QUIT command to the IRC server, with the optional msg as the quit message and closes the connection if the send succeeds.  If msg is the empty string, i.QuitMessage will be used, unless it's also the empty string, in which case no message is sent with the QUIT command.
func (i *IRC) Quit(msg string) error {
	/* Use the stored message if msg is empty */
	if "" == msg && "" != i.QuitMessage {
		msg = i.QuitMessage
	}
	/* Make the message protocolish */
	if "" != msg {
		msg = " :" + msg
	}
	/* Send the quit message */
	if err := i.PrintfLine("QUIT%v", msg); nil != err {
		return err
	}
	/* Close the connection */
	if err := i.S.Close(); nil != err {
		return err
	}

	return nil
}
<|endoftext|>


<commit_before>// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package gcscaching_test

import (
	"testing"
	"time"

	"golang.org/x/net/context"

	"github.com/googlecloudplatform/gcsfuse/timeutil"
	"github.com/jacobsa/gcloud/gcs"
	"github.com/jacobsa/gcloud/gcs/gcscaching"
	"github.com/jacobsa/gcloud/gcs/gcsfake"
	"github.com/jacobsa/gcloud/gcs/gcsutil"
	. "github.com/jacobsa/oglematchers"
	. "github.com/jacobsa/ogletest"
)

func TestIntegration(t *testing.T) { RunTests(t) }

////////////////////////////////////////////////////////////////////////
// Boilerplate
////////////////////////////////////////////////////////////////////////

type IntegrationTest struct {
	ctx context.Context

	cache   gcscaching.StatCache
	clock   timeutil.SimulatedClock
	wrapped gcs.Bucket

	bucket gcs.Bucket
}

func init() { RegisterTestSuite(&IntegrationTest{}) }

func (t *IntegrationTest) SetUp(ti *TestInfo) {
	t.ctx = context.Background()

	// Set up a fixed, non-zero time.
	t.clock.SetTime(time.Date(2015, 4, 5, 2, 15, 0, 0, time.Local))

	// Set up dependencies.
	const cacheCapacity = 100
	t.cache = gcscaching.NewStatCache(cacheCapacity)
	t.wrapped = gcsfake.NewFakeBucket(&t.clock, "some_bucket")

	t.bucket = gcscaching.NewFastStatBucket(
		ttl,
		t.cache,
		&t.clock,
		t.wrapped)
}

func (t *IntegrationTest) stat(name string) (o *gcs.Object, err error) {
	req := &gcs.StatObjectRequest{
		Name: name,
	}

	o, err = t.bucket.StatObject(t.ctx, req)
	return
}

////////////////////////////////////////////////////////////////////////
// Test functions
////////////////////////////////////////////////////////////////////////

func (t *IntegrationTest) StatDoesntCacheNotFoundErrors() {
	const name = "taco"
	var err error

	// Stat an unknown object.
	_, err = t.stat(name)
	AssertThat(err, HasSameTypeAs(&gcs.NotFoundError{}))

	// Create the object through the back door.
	_, err = gcsutil.CreateObject(t.ctx, t.wrapped, name, "")
	AssertEq(nil, err)

	// Stat again. We should now see the object.
	o, err := t.stat(name)
	AssertEq(nil, err)
	ExpectNe(nil, o)
}

func (t *IntegrationTest) CreateInsertsIntoCache() {
	const name = "taco"
	var err error

	// Create an object.
	_, err = gcsutil.CreateObject(t.ctx, t.bucket, name, "")
	AssertEq(nil, err)

	// Delete it through the back door.
	err = t.wrapped.DeleteObject(t.ctx, name)
	AssertEq(nil, err)

	// StatObject should still see it.
	o, err := t.stat(name)
	AssertEq(nil, err)
	ExpectNe(nil, o)
}

func (t *IntegrationTest) StatInsertsIntoCache() {
	const name = "taco"
	var err error

	// Create an object through the back door.
	_, err = gcsutil.CreateObject(t.ctx, t.wrapped, name, "")
	AssertEq(nil, err)

	// Stat it so that it's in cache.
	_, err = t.stat(name)
	AssertEq(nil, err)

	// Delete it through the back door.
	err = t.wrapped.DeleteObject(t.ctx, name)
	AssertEq(nil, err)

	// StatObject should still see it.
	o, err := t.stat(name)
	AssertEq(nil, err)
	ExpectNe(nil, o)
}

func (t *IntegrationTest) ListInsertsIntoCache() {
	const name = "taco"
	var err error

	// Create an object through the back door.
	_, err = gcsutil.CreateObject(t.ctx, t.wrapped, name, "")
	AssertEq(nil, err)

	// List so that it's in cache.
	_, err = t.bucket.ListObjects(t.ctx, &gcs.ListObjectsRequest{})
	AssertEq(nil, err)

	// Delete the object through the back door.
	err = t.wrapped.DeleteObject(t.ctx, name)
	AssertEq(nil, err)

	// StatObject should still see it.
	o, err := t.stat(name)
	AssertEq(nil, err)
	ExpectNe(nil, o)
}

func (t *IntegrationTest) UpdateUpdatesCache() {
	const name = "taco"
	var err error

	// Create an object through the back door.
	_, err = gcsutil.CreateObject(t.ctx, t.wrapped, name, "")
	AssertEq(nil, err)

	// Update it, putting the new version in cache.
	updateReq := &gcs.UpdateObjectRequest{
		Name: name,
	}

	_, err = t.bucket.UpdateObject(t.ctx, updateReq)
	AssertEq(nil, err)

	// Delete the object through the back door.
	err = t.wrapped.DeleteObject(t.ctx, name)
	AssertEq(nil, err)

	// StatObject should still see it.
	o, err := t.stat(name)
	AssertEq(nil, err)
	ExpectNe(nil, o)
}

func (t *IntegrationTest) DeleteRemovesFromCache() {
	AssertFalse(true, "TODO")
}

func (t *IntegrationTest) Expiration() {
	AssertFalse(true, "TODO")
}
<commit_msg>IntegrationTest.Expiration<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package gcscaching_test

import (
	"testing"
	"time"

	"golang.org/x/net/context"

	"github.com/googlecloudplatform/gcsfuse/timeutil"
	"github.com/jacobsa/gcloud/gcs"
	"github.com/jacobsa/gcloud/gcs/gcscaching"
	"github.com/jacobsa/gcloud/gcs/gcsfake"
	"github.com/jacobsa/gcloud/gcs/gcsutil"
	. "github.com/jacobsa/oglematchers"
	. "github.com/jacobsa/ogletest"
)

func TestIntegration(t *testing.T) { RunTests(t) }

////////////////////////////////////////////////////////////////////////
// Boilerplate
////////////////////////////////////////////////////////////////////////

type IntegrationTest struct {
	ctx context.Context

	cache   gcscaching.StatCache
	clock   timeutil.SimulatedClock
	wrapped gcs.Bucket

	bucket gcs.Bucket
}

func init() { RegisterTestSuite(&IntegrationTest{}) }

func (t *IntegrationTest) SetUp(ti *TestInfo) {
	t.ctx = context.Background()

	// Set up a fixed, non-zero time.
	t.clock.SetTime(time.Date(2015, 4, 5, 2, 15, 0, 0, time.Local))

	// Set up dependencies.
	const cacheCapacity = 100
	t.cache = gcscaching.NewStatCache(cacheCapacity)
	t.wrapped = gcsfake.NewFakeBucket(&t.clock, "some_bucket")

	t.bucket = gcscaching.NewFastStatBucket(
		ttl,
		t.cache,
		&t.clock,
		t.wrapped)
}

func (t *IntegrationTest) stat(name string) (o *gcs.Object, err error) {
	req := &gcs.StatObjectRequest{
		Name: name,
	}

	o, err = t.bucket.StatObject(t.ctx, req)
	return
}

////////////////////////////////////////////////////////////////////////
// Test functions
////////////////////////////////////////////////////////////////////////

func (t *IntegrationTest) StatDoesntCacheNotFoundErrors() {
	const name = "taco"
	var err error

	// Stat an unknown object.
	_, err = t.stat(name)
	AssertThat(err, HasSameTypeAs(&gcs.NotFoundError{}))

	// Create the object through the back door.
	_, err = gcsutil.CreateObject(t.ctx, t.wrapped, name, "")
	AssertEq(nil, err)

	// Stat again. We should now see the object.
	o, err := t.stat(name)
	AssertEq(nil, err)
	ExpectNe(nil, o)
}

func (t *IntegrationTest) CreateInsertsIntoCache() {
	const name = "taco"
	var err error

	// Create an object.
	_, err = gcsutil.CreateObject(t.ctx, t.bucket, name, "")
	AssertEq(nil, err)

	// Delete it through the back door.
	err = t.wrapped.DeleteObject(t.ctx, name)
	AssertEq(nil, err)

	// StatObject should still see it.
	o, err := t.stat(name)
	AssertEq(nil, err)
	ExpectNe(nil, o)
}

func (t *IntegrationTest) StatInsertsIntoCache() {
	const name = "taco"
	var err error

	// Create an object through the back door.
	_, err = gcsutil.CreateObject(t.ctx, t.wrapped, name, "")
	AssertEq(nil, err)

	// Stat it so that it's in cache.
	_, err = t.stat(name)
	AssertEq(nil, err)

	// Delete it through the back door.
	err = t.wrapped.DeleteObject(t.ctx, name)
	AssertEq(nil, err)

	// StatObject should still see it.
	o, err := t.stat(name)
	AssertEq(nil, err)
	ExpectNe(nil, o)
}

func (t *IntegrationTest) ListInsertsIntoCache() {
	const name = "taco"
	var err error

	// Create an object through the back door.
	_, err = gcsutil.CreateObject(t.ctx, t.wrapped, name, "")
	AssertEq(nil, err)

	// List so that it's in cache.
	_, err = t.bucket.ListObjects(t.ctx, &gcs.ListObjectsRequest{})
	AssertEq(nil, err)

	// Delete the object through the back door.
	err = t.wrapped.DeleteObject(t.ctx, name)
	AssertEq(nil, err)

	// StatObject should still see it.
	o, err := t.stat(name)
	AssertEq(nil, err)
	ExpectNe(nil, o)
}

func (t *IntegrationTest) UpdateUpdatesCache() {
	const name = "taco"
	var err error

	// Create an object through the back door.
	_, err = gcsutil.CreateObject(t.ctx, t.wrapped, name, "")
	AssertEq(nil, err)

	// Update it, putting the new version in cache.
	updateReq := &gcs.UpdateObjectRequest{
		Name: name,
	}

	_, err = t.bucket.UpdateObject(t.ctx, updateReq)
	AssertEq(nil, err)

	// Delete the object through the back door.
	err = t.wrapped.DeleteObject(t.ctx, name)
	AssertEq(nil, err)

	// StatObject should still see it.
	o, err := t.stat(name)
	AssertEq(nil, err)
	ExpectNe(nil, o)
}

func (t *IntegrationTest) Expiration() {
	const name = "taco"
	var err error

	// Create an object.
	_, err = gcsutil.CreateObject(t.ctx, t.bucket, name, "")
	AssertEq(nil, err)

	// Delete it through the back door.
	err = t.wrapped.DeleteObject(t.ctx, name)
	AssertEq(nil, err)

	// Advance time.
	t.clock.AdvanceTime(ttl + time.Millisecond)

	// StatObject should no longer see it.
	_, err = t.stat(name)
	ExpectThat(err, HasSameTypeAs(&gcs.NotFoundError{}))
}
<|endoftext|>


<commit_before>package pitchforkui

import (
	"strconv"
	pf "trident.li/pitchfork/lib"
)

func h_group_add(cui PfUI) {
	cmd := "group add"
	arg := []string{""}

	msg, err := cui.HandleCmd(cmd, arg)

	var errmsg = ""

	if err != nil {
		/* Failed */
		errmsg = err.Error()
	} else {
		group_name, _ := cui.FormValue("group")
		if group_name != "" {
			/* Success */
			cui.SetRedirect("/group/"+group_name+"/settings/", StatusSeeOther)
			return
		}
	}

	/* Output the page */
	type grpnew struct {
		Group  string `label:"Group Name" pfreq:"yes" hint:"The name of the group"`
		Button string `label:"Create" pftype:"submit"`
	}

	type Page struct {
		*PfPage
		Group   grpnew
		Message string
		Error   string
	}

	var grp grpnew
	p := Page{cui.Page_def(), grp, msg, errmsg}
	cui.Page_show("group/new.tmpl", p)
}

func h_group_settings(cui PfUI) {
	grp := cui.SelectedGroup()

	cmd := "group set"
	arg := []string{grp.GetGroupName()}

	msg, err := cui.HandleForm(cmd, arg, grp)

	var errmsg = ""

	if err != nil {
		/* Failed */
		errmsg = err.Error()
	} else {
		/* Success */
	}

	err = grp.Refresh()
	if err != nil {
		errmsg += err.Error()
	}

	/* Output the page */
	type Page struct {
		*PfPage
		Tg      pf.PfGroup
		Message string
		Error   string
	}

	p := Page{cui.Page_def(), grp, msg, errmsg}
	cui.Page_show("group/settings.tmpl", p)
}

func h_group_log(cui PfUI) {
	grp := cui.SelectedGroup()
	h_system_logA(cui, "", grp.GetGroupName())
}

func h_group_members(cui PfUI) {
	path := cui.GetPath()

	if len(path) != 0 && path[0] != "" {
		H_group_member_profile(cui)
		return
	}

	var err error

	total := 0
	offset := 0

	offset_v, err := cui.FormValue("offset")
	if err == nil && offset_v != "" {
		offset, _ = strconv.Atoi(offset_v)
	}

	search, err := cui.FormValue("search")
	if err != nil {
		search = ""
	}

	grp := cui.SelectedGroup()

	total, err = grp.GetMembersTot(search)
	if err != nil {
		cui.Err("error: " + err.Error())
		return
	}

	members, err := grp.GetMembers(search, cui.TheUser().GetUserName(), offset, 10, false, false)
	if err != nil {
		cui.Err(err.Error())
		return
	}

	/* Output the page */
	type Page struct {
		*PfPage
		Group       pf.PfGroup
		Users       []pf.PfGroupMember
		PagerOffset int
		PagerTotal  int
		Search      string
		IsAdmin     bool
	}

	isadmin := cui.IAmGroupAdmin()

	p := Page{cui.Page_def(), grp, members, offset, total, search, isadmin}
	cui.Page_show("group/members.tmpl", p)
}

func h_group_languages(cui PfUI) {
	H_error(cui, StatusNotImplemented)
}

func group_member_cmd(cui PfUI, cmd string) {
	grp := cui.SelectedGroup()
	user := cui.SelectedUser()

	arg := []string{grp.GetGroupName(), user.GetUserName()}

	_, err := cui.HandleCmd(cmd, arg)
	if err != nil {
		cui.Err(err.Error())
		return
	}

	cui.SetRedirect("/group/"+grp.GetGroupName()+"/members/", StatusSeeOther)
	return
}

func h_group_member_approve(cui PfUI) {
	group_member_cmd(cui, "group member approve")
}

func h_group_member_unblock(cui PfUI) {
	group_member_cmd(cui, "group member unblock")
}

func h_group_member_block(cui PfUI) {
	group_member_cmd(cui, "group member block")
}

func h_group_member_promote(cui PfUI) {
	group_member_cmd(cui, "group member promote")
}

func h_group_member_demote(cui PfUI) {
	group_member_cmd(cui, "group member demote")
}

func h_group_index(cui PfUI) {

	/* Output the page */
	type Page struct {
		*PfPage
		GroupName string
		GroupDesc string
	}

	grp := cui.SelectedGroup()

	p := Page{cui.Page_def(), grp.GetGroupName(), grp.GetGroupDesc()}
	cui.Page_show("group/index.tmpl", p)
}

func h_group_list(cui PfUI) {
	grp := cui.NewGroup()
	var groups []pf.PfGroupUser
	var err error

	if !cui.IsSysAdmin() {
		groups, err = grp.GetGroups(cui.TheUser().GetUserName(), true)
	} else {
		groups, err = grp.GetGroupsAll()
	}

	if err != nil {
		return
	}

	grps := make(map[string]string)
	for i := range groups {
		grps[groups[i].GroupName] = groups[i].GroupDesc
	}

	/* Output the page */
	type Page struct {
		*PfPage
		Groups map[string]string
	}

	menu := NewPfUIMenu([]PfUIMentry{
		{"add", "Add Group", PERM_GROUP_ADMIN, h_group_add, nil},
	})

	cui.SetPageMenu(&menu)

	p := Page{cui.Page_def(), grps}
	cui.Page_show("group/list.tmpl", p)
}

func H_group_member_profile(cui PfUI) {
	path := cui.GetPath()

	/* Select the user */
	err := cui.SelectUser(path[0], PERM_USER_VIEW)
	if err != nil {
		cui.Err("User: " + err.Error())
		H_NoAccess(cui)
		return
	}

	h_user(cui)
	return
}

func h_group_pgp_keys(cui PfUI) {
	grp := cui.SelectedGroup()

	keys, err := grp.GetKeys(cui)
	if err != nil {
		/* Temp redirect to unknown */
		H_error(cui, StatusNotFound)
		return
	}

	fname := grp.GetGroupName() + ".asc"

	cui.SetContentType("application/pgp-keys")
	cui.SetFileName(fname)
	cui.SetExpires(60)
	cui.SetRaw(keys)
}

func h_group_file(cui PfUI) {
	/* Module options */
	pf.Group_FileMod(cui)

	/* Call the module */
	H_file(cui)
}

func h_group_wiki(cui PfUI) {
	/* Module options */
	pf.Group_WikiMod(cui)

	/* Call the module */
	H_wiki(cui)
}

func h_group(cui PfUI) {
	path := cui.GetPath()

	if len(path) == 0 || path[0] == "" {
		cui.SetPageMenu(nil)
		h_group_list(cui)
		return
	}

	/* New group creation */
	if path[0] == "add" && cui.IsSysAdmin() {
		cui.AddCrumb(path[0], "Add Group", "Add Group")
		cui.SetPageMenu(nil)
		h_group_add(cui)
		return
	}

	/* Check member access to group */
	err := cui.SelectGroup(cui.GetPath()[0], PERM_GROUP_MEMBER)
	if err != nil {
		cui.Err("Group: " + err.Error())
		H_NoAccess(cui)
		return
	}

	grp := cui.SelectedGroup()

	cui.AddCrumb(path[0], grp.GetGroupName(), grp.GetGroupDesc())

	cui.SetPath(path[1:])

	/* /group/<grp>/{path} */
	menu := NewPfUIMenu([]PfUIMentry{
		{"", "", PERM_GROUP_MEMBER, h_group_index, nil},
		{"settings", "Settings", PERM_GROUP_ADMIN, h_group_settings, nil},
		{"members", "Members", PERM_GROUP_MEMBER, h_group_members, nil},
		{"pgp_keys", "PGP Keys", PERM_GROUP_MEMBER, h_group_pgp_keys, nil},
		{"ml", "Mailing List", PERM_GROUP_MEMBER, h_ml, nil},
		{"wiki", "Wiki", PERM_GROUP_WIKI, h_group_wiki, nil},
		{"log", "Audit Log", PERM_GROUP_ADMIN, h_group_log, nil},
		{"file", "Files", PERM_GROUP_FILE, h_group_file, nil},
		{"approve", "Approve Member", PERM_GROUP_ADMIN | PERM_HIDDEN | PERM_NOCRUMB, h_group_member_approve, nil},
		{"unblock", "Unblock Member", PERM_GROUP_ADMIN | PERM_HIDDEN | PERM_NOCRUMB, h_group_member_unblock, nil},
		{"block", "Block Member", PERM_GROUP_ADMIN | PERM_HIDDEN | PERM_NOCRUMB, h_group_member_block, nil},
		{"demote", "Demote To Admin", PERM_GROUP_ADMIN | PERM_HIDDEN | PERM_NOCRUMB, h_group_member_demote, nil},
		{"promote", "Promote To Admin", PERM_GROUP_ADMIN | PERM_HIDDEN | PERM_NOCRUMB, h_group_member_promote, nil},
		// TODO: {"calendar", "Calendar", PERM_GROUP_CALENDAR, h_calendar},
	})

	cui.UIMenu(menu)
}
<commit_msg>Move block/unblock/promote/demote under single 'cmd' option<commit_after>package pitchforkui

import (
	"strconv"
	pf "trident.li/pitchfork/lib"
)

func h_group_add(cui PfUI) {
	cmd := "group add"
	arg := []string{""}

	msg, err := cui.HandleCmd(cmd, arg)

	var errmsg = ""

	if err != nil {
		/* Failed */
		errmsg = err.Error()
	} else {
		group_name, _ := cui.FormValue("group")
		if group_name != "" {
			/* Success */
			cui.SetRedirect("/group/"+group_name+"/settings/", StatusSeeOther)
			return
		}
	}

	/* Output the page */
	type grpnew struct {
		Group  string `label:"Group Name" pfreq:"yes" hint:"The name of the group"`
		Button string `label:"Create" pftype:"submit"`
	}

	type Page struct {
		*PfPage
		Group   grpnew
		Message string
		Error   string
	}

	var grp grpnew
	p := Page{cui.Page_def(), grp, msg, errmsg}
	cui.Page_show("group/new.tmpl", p)
}

func h_group_settings(cui PfUI) {
	grp := cui.SelectedGroup()

	cmd := "group set"
	arg := []string{grp.GetGroupName()}

	msg, err := cui.HandleForm(cmd, arg, grp)

	var errmsg = ""

	if err != nil {
		/* Failed */
		errmsg = err.Error()
	} else {
		/* Success */
	}

	err = grp.Refresh()
	if err != nil {
		errmsg += err.Error()
	}

	/* Output the page */
	type Page struct {
		*PfPage
		Tg      pf.PfGroup
		Message string
		Error   string
	}

	p := Page{cui.Page_def(), grp, msg, errmsg}
	cui.Page_show("group/settings.tmpl", p)
}

func h_group_log(cui PfUI) {
	grp := cui.SelectedGroup()
	h_system_logA(cui, "", grp.GetGroupName())
}

func h_group_members(cui PfUI) {
	path := cui.GetPath()

	if len(path) != 0 && path[0] != "" {
		H_group_member_profile(cui)
		return
	}

	var err error

	total := 0
	offset := 0

	offset_v, err := cui.FormValue("offset")
	if err == nil && offset_v != "" {
		offset, _ = strconv.Atoi(offset_v)
	}

	search, err := cui.FormValue("search")
	if err != nil {
		search = ""
	}

	grp := cui.SelectedGroup()

	total, err = grp.GetMembersTot(search)
	if err != nil {
		cui.Err("error: " + err.Error())
		return
	}

	members, err := grp.GetMembers(search, cui.TheUser().GetUserName(), offset, 10, false, cui.IAmGroupAdmin(), false)
	if err != nil {
		cui.Err(err.Error())
		return
	}

	/* Output the page */
	type Page struct {
		*PfPage
		Group       pf.PfGroup
		Members     []pf.PfGroupMember
		PagerOffset int
		PagerTotal  int
		Search      string
		IsAdmin     bool
	}

	isadmin := cui.IAmGroupAdmin()

	p := Page{cui.Page_def(), grp, members, offset, total, search, isadmin}
	cui.Page_show("group/members.tmpl", p)
}

func h_group_languages(cui PfUI) {
	H_error(cui, StatusNotImplemented)
}

func h_group_cmd(cui PfUI) {
	grp := cui.SelectedGroup()

	username, err := cui.FormValue("user")
	if err != nil {
		cui.Errf("Missing parameter user: %s", err.Error())
		return
	}

	groupname, err := cui.FormValue("group")
	if err != nil {
		cui.Errf("Missing parameter group: %s", err.Error())
		return
	}

	if grp.GetGroupName() != groupname {
		cui.Errf("Mismatching group %q vs %q", grp.GetGroupName(), groupname)
		return
	}

	cmd, err := cui.FormValue("cmd")
	if err != nil {
		cui.Errf("Missing parameter cmd: %s", err.Error())
		return
	}

	err = cui.SelectUser(username, PERM_GROUP_ADMIN)
	if err != nil {
		cui.Errf("Could not select user %q: %s", username, err.Error())
		return
	}

	user := cui.SelectedUser()

	switch cmd {
	case "block":
	case "unblock":
	case "promote":
	case "demote":
	default:
		cui.Errf("Unknown Group command: %q", cmd)
		return
	}

	cmd = "group member " + cmd

	/* The arguments */
	arg := []string{grp.GetGroupName(), user.GetUserName()}

	_, err = cui.HandleCmd(cmd, arg)
	if err != nil {
		cui.Err(err.Error())
		return
	}

	cui.SetRedirect("/group/"+grp.GetGroupName()+"/members/", StatusSeeOther)
	return
}

func h_group_index(cui PfUI) {

	/* Output the page */
	type Page struct {
		*PfPage
		GroupName string
		GroupDesc string
	}

	grp := cui.SelectedGroup()

	p := Page{cui.Page_def(), grp.GetGroupName(), grp.GetGroupDesc()}
	cui.Page_show("group/index.tmpl", p)
}

func h_group_list(cui PfUI) {
	grp := cui.NewGroup()
	var grusers []pf.PfGroupUser
	var err error

	if !cui.IsSysAdmin() {
		grusers, err = grp.GetGroups(cui, cui.TheUser().GetUserName())
	} else {
		grusers, err = grp.GetGroupsAll()
	}

	if err != nil {
		return
	}

	grps := make(map[string]string)
	for _, gru := range grusers {
		grps[gru.GroupName] = gru.GroupDesc
	}

	/* Output the page */
	type Page struct {
		*PfPage
		Groups map[string]string
	}

	menu := NewPfUIMenu([]PfUIMentry{
		{"add", "Add Group", PERM_GROUP_ADMIN, h_group_add, nil},
	})

	cui.SetPageMenu(&menu)

	p := Page{cui.Page_def(), grps}
	cui.Page_show("group/list.tmpl", p)
}

func H_group_member_profile(cui PfUI) {
	path := cui.GetPath()

	/* Select the user */
	err := cui.SelectUser(path[0], PERM_USER_VIEW)
	if err != nil {
		cui.Err("User: " + err.Error())
		H_NoAccess(cui)
		return
	}

	h_user(cui)
	return
}

func h_group_pgp_keys(cui PfUI) {
	grp := cui.SelectedGroup()

	keys, err := grp.GetKeys(cui)
	if err != nil {
		/* Temp redirect to unknown */
		H_error(cui, StatusNotFound)
		return
	}

	fname := grp.GetGroupName() + ".asc"

	cui.SetContentType("application/pgp-keys")
	cui.SetFileName(fname)
	cui.SetExpires(60)
	cui.SetRaw(keys)
}

func h_group_file(cui PfUI) {
	/* Module options */
	pf.Group_FileMod(cui)

	/* Call the module */
	H_file(cui)
}

func h_group_wiki(cui PfUI) {
	/* Module options */
	pf.Group_WikiMod(cui)

	/* Call the module */
	H_wiki(cui)
}

func h_group(cui PfUI) {
	path := cui.GetPath()

	if len(path) == 0 || path[0] == "" {
		cui.SetPageMenu(nil)
		h_group_list(cui)
		return
	}

	/* New group creation */
	if path[0] == "add" && cui.IsSysAdmin() {
		cui.AddCrumb(path[0], "Add Group", "Add Group")
		cui.SetPageMenu(nil)
		h_group_add(cui)
		return
	}

	/* Check member access to group */
	err := cui.SelectGroup(cui.GetPath()[0], PERM_GROUP_MEMBER)
	if err != nil {
		cui.Err("Group: " + err.Error())
		H_NoAccess(cui)
		return
	}

	grp := cui.SelectedGroup()

	cui.AddCrumb(path[0], grp.GetGroupName(), grp.GetGroupDesc())

	cui.SetPath(path[1:])

	/* /group/<grp>/{path} */
	menu := NewPfUIMenu([]PfUIMentry{
		{"", "", PERM_GROUP_MEMBER, h_group_index, nil},
		{"settings", "Settings", PERM_GROUP_ADMIN, h_group_settings, nil},
		{"members", "Members", PERM_GROUP_MEMBER, h_group_members, nil},
		{"pgp_keys", "PGP Keys", PERM_GROUP_MEMBER, h_group_pgp_keys, nil},
		{"ml", "Mailing List", PERM_GROUP_MEMBER, h_ml, nil},
		{"wiki", "Wiki", PERM_GROUP_WIKI, h_group_wiki, nil},
		{"log", "Audit Log", PERM_GROUP_ADMIN, h_group_log, nil},
		{"file", "Files", PERM_GROUP_FILE, h_group_file, nil},
		{"cmd", "Commands", PERM_GROUP_ADMIN | PERM_HIDDEN | PERM_NOCRUMB, h_group_cmd, nil},
		// TODO: {"calendar", "Calendar", PERM_GROUP_CALENDAR, h_calendar},
	})

	cui.UIMenu(menu)
}
<|endoftext|>


<commit_before>// Code generated by 'go generate'
// source: fmts/gendoc.go
// DO NOT EDIT!
// Please run '$ go generate ./...' instead to update this file

// Package fmts holds defined errorformats.
//
// Defined formats:
//
// 	css
// 		stylelint	A mighty modern CSS linter - https://github.com/stylelint/stylelint
// 	go
// 		golint	linter for Go source code - https://github.com/golang/lint
// 		govet	Vet examines Go source code and reports suspicious problems - https://golang.org/cmd/vet/
// 	javascript
// 		eslint	(eslint [-f stylish]) A fully pluggable tool for identifying and reporting on patterns in JavaScript - https://github.com/eslint/eslint
// 		eslint-compact	(eslint -f compact) A fully pluggable tool for identifying and reporting on patterns in JavaScript - https://github.com/eslint/eslint
// 	php
// 		phpstan	(phpstan --errorFormat=raw) PHP Static Analysis Tool - discover bugs in your code without running it! - https://github.com/phpstan/phpstan
// 	puppet
// 		puppet-lint	Check that your Puppet manifests conform to the style guide - https://github.com/rodjek/puppet-lint
// 	python
// 		pep8	Python style guide checker - https://pypi.python.org/pypi/pep8
// 	ruby
// 		rubocop	A Ruby static code analyzer, based on the community Ruby style guide - https://github.com/rubocop-hq/rubocop
// 	scala
// 		sbt	the interactive build tool - http://www.scala-sbt.org/
// 		sbt-scalastyle	Scalastyle - SBT plugin - http://www.scalastyle.org/sbt.html
// 		scalac	Scala compiler - http://www.scala-lang.org/
// 		scalastyle	Scalastyle - Command line - http://www.scalastyle.org/command-line.html
// 	typescript
// 		tsc	TypeScript compiler - https://www.typescriptlang.org/
// 		tslint	An extensible linter for the TypeScript language - https://github.com/palantir/tslint
package fmts
<commit_msg>Run go generate<commit_after>// Code generated by 'go generate'
// source: fmts/gendoc.go
// DO NOT EDIT!
// Please run '$ go generate ./...' instead to update this file

// Package fmts holds defined errorformats.
//
// Defined formats:
//
// 	css
// 		stylelint	A mighty modern CSS linter - https://github.com/stylelint/stylelint
// 	go
// 		golangci-lint	(golangci-lint run --out-format=line-number) GolangCI-Lint is a linters aggregator. - https://github.com/golangci/golangci-lint
// 		golint	linter for Go source code - https://github.com/golang/lint
// 		govet	Vet examines Go source code and reports suspicious problems - https://golang.org/cmd/vet/
// 	javascript
// 		eslint	(eslint [-f stylish]) A fully pluggable tool for identifying and reporting on patterns in JavaScript - https://github.com/eslint/eslint
// 		eslint-compact	(eslint -f compact) A fully pluggable tool for identifying and reporting on patterns in JavaScript - https://github.com/eslint/eslint
// 	php
// 		phpstan	(phpstan --errorFormat=raw) PHP Static Analysis Tool - discover bugs in your code without running it! - https://github.com/phpstan/phpstan
// 	puppet
// 		puppet-lint	Check that your Puppet manifests conform to the style guide - https://github.com/rodjek/puppet-lint
// 	python
// 		pep8	Python style guide checker - https://pypi.python.org/pypi/pep8
// 	ruby
// 		rubocop	A Ruby static code analyzer, based on the community Ruby style guide - https://github.com/rubocop-hq/rubocop
// 	scala
// 		sbt	the interactive build tool - http://www.scala-sbt.org/
// 		sbt-scalastyle	Scalastyle - SBT plugin - http://www.scalastyle.org/sbt.html
// 		scalac	Scala compiler - http://www.scala-lang.org/
// 		scalastyle	Scalastyle - Command line - http://www.scalastyle.org/command-line.html
// 	typescript
// 		tsc	TypeScript compiler - https://www.typescriptlang.org/
// 		tslint	An extensible linter for the TypeScript language - https://github.com/palantir/tslint
package fmts
<|endoftext|>


<commit_before>package models

import (
	"errors"
	"fmt"

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

type Account struct {
	// unique id of the account
	Id int64 `json:"id"`

	// old id of the account, which is originally
	// perisisted in mongo
	// mongo ids has 24 char
	OldId string `json:"oldId"      sql:"NOT NULL;UNIQUE;TYPE:VARCHAR(24);"`
}

func NewAccount() *Account {
	return &Account{}
}

func (a *Account) GetId() int64 {
	return a.Id
}

func (a Account) TableName() string {
	return "api.account"
}

func (a *Account) One(q *bongo.Query) error {
	return bongo.B.One(a, a, q)
}

func (a *Account) ById(id int64) error {
	return bongo.B.ById(a, id)
}

func (a *Account) FetchOrCreate() error {
	if a.OldId == "" {
		return errors.New("old id is not set")
	}

	selector := map[string]interface{}{
		"old_id": a.OldId,
	}

	err := a.One(bongo.NewQS(selector))
	// if we dont get any error
	// it means we found the record in our db
	if err == nil {
		return nil
	}

	// first check if the err is not found err
	if err == gorm.RecordNotFound {
		if err := a.Create(); err != nil {
			return err
		}
		return nil
	}

	return err
}

func (a *Account) Create() error {
	if a.OldId == "" {
		return errors.New("old id is not set")
	}

	return bongo.B.Create(a)
}

func (a *Account) Delete() error {
	return bongo.B.Delete(a)
}

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

func (a *Account) FetchChannels(q *Query) ([]Channel, error) {
	cp := NewChannelParticipant()
	// fetch channel ids
	cids, err := cp.FetchParticipatedChannelIds(a, q)
	if err != nil {
		return nil, err
	}

	// fetch channels by their ids
	channels, err := NewChannel().FetchByIds(cids)
	if err != nil {
		return nil, err
	}

	return channels, nil
}

func (a *Account) Follow(targetId int64) (*ChannelParticipant, error) {
	c, err := a.FetchChannel(Channel_TYPE_FOLLOWERS)
	if err == nil {
		return c.AddParticipant(targetId)
	}

	if err == gorm.RecordNotFound {
		c, err := a.CreateFollowingFeedChannel()
		if err != nil {
			return nil, err
		}
		return c.AddParticipant(targetId)
	}

	return nil, err
}

func (a *Account) Unfollow(targetId int64) (*Account, error) {
	c, err := a.FetchChannel(Channel_TYPE_FOLLOWERS)
	if err != nil {
		return nil, err
	}

	return a, c.RemoveParticipant(targetId)
}

func (a *Account) FetchFollowerIds() ([]int64, error) {
	followerIds := make([]int64, 0)
	if a.Id == 0 {
		return nil, errors.New(
			"Account id is not set for FetchFollowerChannelIds function ",
		)
	}

	c, err := a.FetchChannel(Channel_TYPE_FOLLOWERS)
	if err != nil {
		return followerIds, err
	}

	participants, err := c.FetchParticipantIds()
	if err != nil {
		return followerIds, err
	}

	return participants, nil
}

func (a *Account) FetchChannel(channelType string) (*Channel, error) {
	if a.Id == 0 {
		return nil, errors.New("Account id is not set")
	}

	c := NewChannel()
	selector := map[string]interface{}{
		"creator_id":    a.Id,
		"type_constant": channelType,
	}

	if err := c.One(bongo.NewQS(selector)); err != nil {
		return nil, err
	}

	return c, nil
}

func (a *Account) CreateFollowingFeedChannel() (*Channel, error) {
	if a.Id == 0 {
		return nil, errors.New("Account id is not set")
	}

	c := NewChannel()
	c.CreatorId = a.Id
	c.Name = fmt.Sprintf("%d-FollowingFeedChannel", a.Id)
	c.GroupName = Channel_KODING_NAME
	c.Purpose = "Following Feed for Me"
	c.TypeConstant = Channel_TYPE_FOLLOWERS
	if err := c.Create(); err != nil {
		return nil, err
	}

	return c, nil
}

func (a *Account) FetchFollowerChannelIds() ([]int64, error) {

	followerIds, err := a.FetchFollowerIds()
	if err != nil {
		return nil, err
	}

	cp := NewChannelParticipant()
	var channelIds []int64
	err = bongo.B.DB.
		Table(cp.TableName()).
		Where(
		"creator_id IN (?) and type_constant = ?",
		followerIds,
		Channel_TYPE_FOLLOWINGFEED,
	).Find(&channelIds).Error

	if err != nil {
		return nil, err
	}

	return channelIds, nil
}

func FetchOdlIdByAccountId(accountId int64) (string, error) {

	a := NewAccount()
	var data []string
	q := &bongo.Query{
		Selector: map[string]interface{}{
			"id": accountId,
		},
		Pluck: "old_id",
		Limit: 1,
	}
	err := a.Some(&data, q)
	if err != nil {
		return "", err
	}

	if len(data) == 0 {
		return "", nil
	}

	return data[0], nil
}

func FetchOldIdsByAccountIds(accountIds []int64) ([]string, error) {
	var oldIds []string
	if len(accountIds) == 0 {
		return make([]string, 0), nil
	}
	a := NewAccount()
	err := bongo.B.DB.
		Table(a.TableName()).
		Where("id IN (?)", accountIds).
		Pluck("old_id", &oldIds).Error

	if err != nil {
		return make([]string, 0), err
	}

	if len(oldIds) == 0 {
		return make([]string, 0), nil
	}

	return oldIds, err
}
<commit_msg>Social: we are gonna remove any account from our database<commit_after>package models

import (
	"errors"
	"fmt"

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

type Account struct {
	// unique id of the account
	Id int64 `json:"id"`

	// old id of the account, which is originally
	// perisisted in mongo
	// mongo ids has 24 char
	OldId string `json:"oldId"      sql:"NOT NULL;UNIQUE;TYPE:VARCHAR(24);"`
}

func NewAccount() *Account {
	return &Account{}
}

func (a *Account) GetId() int64 {
	return a.Id
}

func (a Account) TableName() string {
	return "api.account"
}

func (a *Account) One(q *bongo.Query) error {
	return bongo.B.One(a, a, q)
}

func (a *Account) ById(id int64) error {
	return bongo.B.ById(a, id)
}

func (a *Account) FetchOrCreate() error {
	if a.OldId == "" {
		return errors.New("old id is not set")
	}

	selector := map[string]interface{}{
		"old_id": a.OldId,
	}

	err := a.One(bongo.NewQS(selector))
	// if we dont get any error
	// it means we found the record in our db
	if err == nil {
		return nil
	}

	// first check if the err is not found err
	if err == gorm.RecordNotFound {
		if err := a.Create(); err != nil {
			return err
		}
		return nil
	}

	return err
}

func (a *Account) Create() error {
	if a.OldId == "" {
		return errors.New("old id is not set")
	}

	return bongo.B.Create(a)
}

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

func (a *Account) FetchChannels(q *Query) ([]Channel, error) {
	cp := NewChannelParticipant()
	// fetch channel ids
	cids, err := cp.FetchParticipatedChannelIds(a, q)
	if err != nil {
		return nil, err
	}

	// fetch channels by their ids
	channels, err := NewChannel().FetchByIds(cids)
	if err != nil {
		return nil, err
	}

	return channels, nil
}

func (a *Account) Follow(targetId int64) (*ChannelParticipant, error) {
	c, err := a.FetchChannel(Channel_TYPE_FOLLOWERS)
	if err == nil {
		return c.AddParticipant(targetId)
	}

	if err == gorm.RecordNotFound {
		c, err := a.CreateFollowingFeedChannel()
		if err != nil {
			return nil, err
		}
		return c.AddParticipant(targetId)
	}

	return nil, err
}

func (a *Account) Unfollow(targetId int64) (*Account, error) {
	c, err := a.FetchChannel(Channel_TYPE_FOLLOWERS)
	if err != nil {
		return nil, err
	}

	return a, c.RemoveParticipant(targetId)
}

func (a *Account) FetchFollowerIds() ([]int64, error) {
	followerIds := make([]int64, 0)
	if a.Id == 0 {
		return nil, errors.New(
			"Account id is not set for FetchFollowerChannelIds function ",
		)
	}

	c, err := a.FetchChannel(Channel_TYPE_FOLLOWERS)
	if err != nil {
		return followerIds, err
	}

	participants, err := c.FetchParticipantIds()
	if err != nil {
		return followerIds, err
	}

	return participants, nil
}

func (a *Account) FetchChannel(channelType string) (*Channel, error) {
	if a.Id == 0 {
		return nil, errors.New("Account id is not set")
	}

	c := NewChannel()
	selector := map[string]interface{}{
		"creator_id":    a.Id,
		"type_constant": channelType,
	}

	if err := c.One(bongo.NewQS(selector)); err != nil {
		return nil, err
	}

	return c, nil
}

func (a *Account) CreateFollowingFeedChannel() (*Channel, error) {
	if a.Id == 0 {
		return nil, errors.New("Account id is not set")
	}

	c := NewChannel()
	c.CreatorId = a.Id
	c.Name = fmt.Sprintf("%d-FollowingFeedChannel", a.Id)
	c.GroupName = Channel_KODING_NAME
	c.Purpose = "Following Feed for Me"
	c.TypeConstant = Channel_TYPE_FOLLOWERS
	if err := c.Create(); err != nil {
		return nil, err
	}

	return c, nil
}

func (a *Account) FetchFollowerChannelIds() ([]int64, error) {

	followerIds, err := a.FetchFollowerIds()
	if err != nil {
		return nil, err
	}

	cp := NewChannelParticipant()
	var channelIds []int64
	err = bongo.B.DB.
		Table(cp.TableName()).
		Where(
		"creator_id IN (?) and type_constant = ?",
		followerIds,
		Channel_TYPE_FOLLOWINGFEED,
	).Find(&channelIds).Error

	if err != nil {
		return nil, err
	}

	return channelIds, nil
}

func FetchOdlIdByAccountId(accountId int64) (string, error) {

	a := NewAccount()
	var data []string
	q := &bongo.Query{
		Selector: map[string]interface{}{
			"id": accountId,
		},
		Pluck: "old_id",
		Limit: 1,
	}
	err := a.Some(&data, q)
	if err != nil {
		return "", err
	}

	if len(data) == 0 {
		return "", nil
	}

	return data[0], nil
}

func FetchOldIdsByAccountIds(accountIds []int64) ([]string, error) {
	var oldIds []string
	if len(accountIds) == 0 {
		return make([]string, 0), nil
	}
	a := NewAccount()
	err := bongo.B.DB.
		Table(a.TableName()).
		Where("id IN (?)", accountIds).
		Pluck("old_id", &oldIds).Error

	if err != nil {
		return make([]string, 0), err
	}

	if len(oldIds) == 0 {
		return make([]string, 0), nil
	}

	return oldIds, err
}
<|endoftext|>


<commit_before>package heartbeat

import (
	"context"
	"fmt"
	"sync"
	"time"

	"github.com/golang/glog"
	"github.com/youtube/vitess/go/vt/dbconfigs"
	"github.com/youtube/vitess/go/vt/dbconnpool"
	"github.com/youtube/vitess/go/vt/proto/topodata"
	"github.com/youtube/vitess/go/vt/topo"
	"github.com/youtube/vitess/go/vt/vttablet/tabletserver/tabletenv"
)

// Writer is the engine for reading and writing heartbeats
type Writer struct {
	mu      sync.Mutex
	isOpen  bool
	cancel  context.CancelFunc
	wg      sync.WaitGroup
	inError bool

	topoServer  topo.Server
	tabletAlias topodata.TabletAlias
	now         func() time.Time
	conn        *dbconnpool.DBConnection
}

// NewWriter creates a new Engine
func NewWriter(topoServer topo.Server, alias topodata.TabletAlias) *Writer {
	return &Writer{
		topoServer:  topoServer,
		tabletAlias: alias,
		now:         time.Now,
	}
}

// Open starts the Engine service
func (me *Writer) Open(dbc dbconfigs.DBConfigs) error {
	if !*enableHeartbeat {
		return nil
	}

	me.mu.Lock()
	defer me.mu.Unlock()
	if me.isOpen {
		return nil
	}

	allPrivs, err := dbconfigs.WithCredentials(&dbc.AllPrivs)
	if err != nil {
		return fmt.Errorf("Failed to get credentials for heartbeat: %v", err)
	}
	conn, err := dbconnpool.NewDBConnection(&allPrivs, tabletenv.MySQLStats)
	if err != nil {
		return fmt.Errorf("Failed to create connection for heartbeat: %v", err)
	}
	me.conn = conn
	ctx, cancel := context.WithCancel(tabletenv.LocalContext())
	me.cancel = cancel
	me.wg.Add(1)
	go me.run(ctx)

	me.isOpen = true
	return nil
}

// Close closes the Engine service
func (me *Writer) Close() {
	me.mu.Lock()
	defer me.mu.Unlock()
	if !me.isOpen {
		return
	}
	me.conn.Close()
	me.cancel()
	me.wg.Wait()
	me.isOpen = false
}

func (me *Writer) run(ctx context.Context) {
	defer func() {
		tabletenv.LogError()
		me.wg.Done()
	}()

	me.waitForHeartbeatTable(ctx)

	for {
		me.writeHeartbeat()
		if waitOrExit(ctx, *interval) {
			return
		}
	}
}

func (me *Writer) waitForHeartbeatTable(ctx context.Context) {
	glog.Info("Initializing heartbeat table")
	for {
		err := me.initHeartbeatTable()
		if err != nil {
			me.recordError("Failed to initialize heartbeat table: %v", err)
			if waitOrExit(ctx, 10*time.Second) {
				return
			}
		}
		return
	}
}

func (me *Writer) initHeartbeatTable() error {
	_, err := me.conn.ExecuteFetch("CREATE DATABASE IF NOT EXISTS _vt", 0, false)
	if err != nil {
		return err
	}
	_, err = me.conn.ExecuteFetch("CREATE TABLE IF NOT EXISTS _vt.heartbeat (ts bigint NOT NULL, master_uid int unsigned NOT NULL PRIMARY KEY)", 0, false)
	if err != nil {
		return err
	}
	_, err = me.conn.ExecuteFetch(fmt.Sprintf("INSERT INTO _vt.heartbeat (ts, master_uid) VALUES (%d, %d) ON DUPLICATE KEY UPDATE ts=VALUES(ts)", me.now().UnixNano(), me.tabletAlias.Uid), 0, false)
	return err
}

func (me *Writer) writeHeartbeat() {
	updateQuery := "UPDATE _vt.heartbeat SET ts=%d WHERE master_uid=%d"
	_, err := me.conn.ExecuteFetch(fmt.Sprintf(updateQuery, me.now().Nanosecond(), me.tabletAlias.Uid), 0, false)
	if err != nil {
		me.recordError("Failed to update heartbeat: %v", err)
		return
	}
	me.inError = false
	counters.Add("Writes", 1)
}

func (me *Writer) recordError(formatString string, err error) {
	if !me.inError {
		glog.Errorf(formatString, err)
	}
	counters.Add("Errors", 1)
	me.inError = true
}
<commit_msg>wrong function<commit_after>package heartbeat

import (
	"context"
	"fmt"
	"sync"
	"time"

	"github.com/golang/glog"
	"github.com/youtube/vitess/go/vt/dbconfigs"
	"github.com/youtube/vitess/go/vt/dbconnpool"
	"github.com/youtube/vitess/go/vt/proto/topodata"
	"github.com/youtube/vitess/go/vt/topo"
	"github.com/youtube/vitess/go/vt/vttablet/tabletserver/tabletenv"
)

// Writer is the engine for reading and writing heartbeats
type Writer struct {
	mu      sync.Mutex
	isOpen  bool
	cancel  context.CancelFunc
	wg      sync.WaitGroup
	inError bool

	topoServer  topo.Server
	tabletAlias topodata.TabletAlias
	now         func() time.Time
	conn        *dbconnpool.DBConnection
}

// NewWriter creates a new Engine
func NewWriter(topoServer topo.Server, alias topodata.TabletAlias) *Writer {
	return &Writer{
		topoServer:  topoServer,
		tabletAlias: alias,
		now:         time.Now,
	}
}

// Open starts the Engine service
func (me *Writer) Open(dbc dbconfigs.DBConfigs) error {
	if !*enableHeartbeat {
		return nil
	}

	me.mu.Lock()
	defer me.mu.Unlock()
	if me.isOpen {
		return nil
	}

	allPrivs, err := dbconfigs.WithCredentials(&dbc.AllPrivs)
	if err != nil {
		return fmt.Errorf("Failed to get credentials for heartbeat: %v", err)
	}
	conn, err := dbconnpool.NewDBConnection(&allPrivs, tabletenv.MySQLStats)
	if err != nil {
		return fmt.Errorf("Failed to create connection for heartbeat: %v", err)
	}
	me.conn = conn
	ctx, cancel := context.WithCancel(tabletenv.LocalContext())
	me.cancel = cancel
	me.wg.Add(1)
	go me.run(ctx)

	me.isOpen = true
	return nil
}

// Close closes the Engine service
func (me *Writer) Close() {
	me.mu.Lock()
	defer me.mu.Unlock()
	if !me.isOpen {
		return
	}
	me.conn.Close()
	me.cancel()
	me.wg.Wait()
	me.isOpen = false
}

func (me *Writer) run(ctx context.Context) {
	defer func() {
		tabletenv.LogError()
		me.wg.Done()
	}()

	me.waitForHeartbeatTable(ctx)

	for {
		me.writeHeartbeat()
		if waitOrExit(ctx, *interval) {
			return
		}
	}
}

func (me *Writer) waitForHeartbeatTable(ctx context.Context) {
	glog.Info("Initializing heartbeat table")
	for {
		err := me.initHeartbeatTable()
		if err != nil {
			me.recordError("Failed to initialize heartbeat table: %v", err)
			if waitOrExit(ctx, 10*time.Second) {
				return
			}
		}
		return
	}
}

func (me *Writer) initHeartbeatTable() error {
	_, err := me.conn.ExecuteFetch("CREATE DATABASE IF NOT EXISTS _vt", 0, false)
	if err != nil {
		return err
	}
	_, err = me.conn.ExecuteFetch("CREATE TABLE IF NOT EXISTS _vt.heartbeat (ts bigint NOT NULL, master_uid int unsigned NOT NULL PRIMARY KEY)", 0, false)
	if err != nil {
		return err
	}
	_, err = me.conn.ExecuteFetch(fmt.Sprintf("INSERT INTO _vt.heartbeat (ts, master_uid) VALUES (%d, %d) ON DUPLICATE KEY UPDATE ts=VALUES(ts)", me.now().UnixNano(), me.tabletAlias.Uid), 0, false)
	return err
}

func (me *Writer) writeHeartbeat() {
	updateQuery := "UPDATE _vt.heartbeat SET ts=%d WHERE master_uid=%d"
	_, err := me.conn.ExecuteFetch(fmt.Sprintf(updateQuery, me.now().UnixNano(), me.tabletAlias.Uid), 0, false)
	if err != nil {
		me.recordError("Failed to update heartbeat: %v", err)
		return
	}
	me.inError = false
	counters.Add("Writes", 1)
}

func (me *Writer) recordError(formatString string, err error) {
	if !me.inError {
		glog.Errorf(formatString, err)
	}
	counters.Add("Errors", 1)
	me.inError = true
}
<|endoftext|>


<commit_before>/* syncwatcher

   This package is a recursive wrapper for fsnotify.
   The interface is intended to be compatible with fsnotify.

   When a directory is "Watch"ed, so are all its subdirectories

   When a watched directory is moved, within, into, or out of, another watched
   directory, it is unwatched and (re)watched, as appropriate. As a special
   case, each root directory (as passed to "Watch"), is never unwatched, even
   if deleted or moved.

   WARNING: when a directory is moved there is a brief period in which other
   events inside that directory may be missed. You should assume that anything
   may have happened in that time.
*/

package main

import (
	"github.com/go-fsnotify/fsnotify"
	"errors"
	"os"
	"path/filepath"
	"strings"
	"sync"
)

type SyncWatcher struct {
	Error chan error
	Event chan fsnotify.Event

	watcher   	*fsnotify.Watcher
	paths     	map[string]string
	mainPath	string
	ignorePaths 	[]string
	ignorePatterns 	[]Pattern
	roots     	map[string]int
	pathMutex 	*sync.Mutex
}

func NewSyncWatcher(mainPath string, ignorePaths []string, ignorePatterns []Pattern) (*SyncWatcher, error) {
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		return nil, err
	}

	sw := &SyncWatcher{
		make(chan error),
		make(chan fsnotify.Event),
		watcher,
		make(map[string]string),
		mainPath,
		ignorePaths,
		ignorePatterns,
		make(map[string]int),
		&sync.Mutex{},
	}

	// Handle events from fsnotify, deal with them
	// and forward the interesting ones to the caller
	go func() {
		var (
			ev  fsnotify.Event
			err error
		)
		// Loop until both incoming channels are closed
		for openEvent, openErr := true, true; openEvent || openErr; {
			select {
			case ev, openEvent = <-watcher.Events:
				if openEvent {
					// Add or remove watches as appropriate
					sw.pathMutex.Lock()
					_, present := sw.paths[ev.Name]
					sw.pathMutex.Unlock()
					if present {
						// If we recognise the path then it must be a directory
						// that means its changed, and the old watches must be
						// removed.  New watches will be added when the corresponding
						// "create" event arrives.
						// This uses "removeWatch" not "RemoveWatch" on purpose
						sw.removeWatch(ev.Name)
					} else if info, err := os.Lstat(ev.Name); err == nil && info.IsDir() {
						// A new, unrecognised directory was created.
						sw.watch(ev.Name)
					}

					// Forward the event to our client.
					sw.Event <- ev
				}
			case err, openErr = <-watcher.Errors:
				if openErr {
					// Forward error events to our client
					sw.Error <- err
				}
			}
		}
		// If we get here then the incoming channels are closed,
		// so close the outgoing channels.
		close(sw.Event)
		close(sw.Error)
	}()

	return sw, nil
}

func (w *SyncWatcher) Close() error {
	// We close the fsnotify watcher.
	// That will close our incoming channels, and so close the SyncWatcher
	// indirectly.
	err := w.watcher.Close()
	if err != nil {
		return err
	}
	return nil
}

// This is like RemoveWatch except that it does not unwatch the root directory.
func (w *SyncWatcher) removeWatch(path string) error {
	w.pathMutex.Lock()
	defer w.pathMutex.Unlock()

	// Recursively remove all the watches from the given directory, and its
	// subdirectories. The root directory will not be unwatched (RemoveWatch
	// takes care of that).
	var recursive_remove func(dir string) error
	recursive_remove = func(dir string) error {
		children, ok := w.paths[dir]
		if ok {
			for _, child := range strings.Split(children, "\000") {
				if len(child) > 0 {
					// deliberately ignore errors from child watches
					recursive_remove(filepath.Join(dir, child))
				}
			}
			if _, isroot := w.roots[dir]; !isroot {
				delete(w.paths, dir)
				return w.watcher.Remove(dir)
			}
		}
		return errors.New("cannot remove uknown watch: " + dir)
	}

	return recursive_remove(path)
}

func (w *SyncWatcher) RemoveWatch(path string) error {
	// We want to unwatch the whole tree, including to root.
	// If we unregister the root then removeWatch will take care of the rest.
	w.pathMutex.Lock()
	if _, isroot := w.roots[path]; isroot {
		delete(w.roots, path)
	}
	w.pathMutex.Unlock()
	return w.removeWatch(path)
}

func (w *SyncWatcher) watch(path string) error {
	w.pathMutex.Lock()
	defer w.pathMutex.Unlock()

	return filepath.Walk(path, func(p string, info os.FileInfo, err error) error {
		if err == nil && info.IsDir() {
			if shouldIgnore(w.mainPath, w.ignorePaths, w.ignorePatterns, p) {
				return err
			}
			err = w.watcher.Add(p)
			if err == nil {
				w.paths[p] = ""
				parent := filepath.Dir(p)
				if _, ok := w.paths[parent]; ok {
					// Record the directory structure so that it can be
					// walked again when we need to remove the watches.
					w.paths[parent] += filepath.Base(p) + "\000"
				}
			}
		}
		return err
	})
}

func (w *SyncWatcher) Watch(path string) error {
	w.pathMutex.Lock()
	_, present := w.paths[path]

	if present {
		w.pathMutex.Unlock()
		return errors.New("cannot watch path twice: " + path)
	}
	w.roots[path] = 1
	w.pathMutex.Unlock()

	return w.watch(path)
}

func (w *SyncWatcher) String() string {
	w.pathMutex.Lock()
	defer w.pathMutex.Unlock()

	str := "SyncWatch:"
	for path := range w.paths {
		str += " " + path + " \"" + w.paths[path] + "\""
	}
	return str
}
<commit_msg>Keep event references in local scope<commit_after>/* syncwatcher

   This package is a recursive wrapper for fsnotify.
   The interface is intended to be compatible with fsnotify.

   When a directory is "Watch"ed, so are all its subdirectories

   When a watched directory is moved, within, into, or out of, another watched
   directory, it is unwatched and (re)watched, as appropriate. As a special
   case, each root directory (as passed to "Watch"), is never unwatched, even
   if deleted or moved.

   WARNING: when a directory is moved there is a brief period in which other
   events inside that directory may be missed. You should assume that anything
   may have happened in that time.
*/

package main

import (
	"github.com/go-fsnotify/fsnotify"
	"errors"
	"os"
	"path/filepath"
	"strings"
	"sync"
)

type SyncWatcher struct {
	Error chan error
	Event chan fsnotify.Event

	watcher   	*fsnotify.Watcher
	paths     	map[string]string
	mainPath	string
	ignorePaths 	[]string
	ignorePatterns 	[]Pattern
	roots     	map[string]int
	pathMutex 	*sync.Mutex
}

func NewSyncWatcher(mainPath string, ignorePaths []string, ignorePatterns []Pattern) (*SyncWatcher, error) {
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		return nil, err
	}

	sw := &SyncWatcher{
		make(chan error),
		make(chan fsnotify.Event),
		watcher,
		make(map[string]string),
		mainPath,
		ignorePaths,
		ignorePatterns,
		make(map[string]int),
		&sync.Mutex{},
	}

	// Handle events from fsnotify, deal with them
	// and forward the interesting ones to the caller
	go func() {
		// Loop until both incoming channels are closed
		for openEvent, openErr := true, true; openEvent || openErr; {
			select {
			case ev, openEvent := <-watcher.Events:
				if openEvent {
					// Add or remove watches as appropriate
					sw.pathMutex.Lock()
					_, present := sw.paths[ev.Name]
					sw.pathMutex.Unlock()
					if present {
						// If we recognise the path then it must be a directory
						// that means its changed, and the old watches must be
						// removed.  New watches will be added when the corresponding
						// "create" event arrives.
						// This uses "removeWatch" not "RemoveWatch" on purpose
						sw.removeWatch(ev.Name)
					} else if info, err := os.Lstat(ev.Name); err == nil && info.IsDir() {
						// A new, unrecognised directory was created.
						sw.watch(ev.Name)
					}

					// Forward the event to our client.
					sw.Event <- ev
				}
			case err, openErr = <-watcher.Errors:
				if openErr {
					// Forward error events to our client
					sw.Error <- err
				}
			}
		}
		// If we get here then the incoming channels are closed,
		// so close the outgoing channels.
		close(sw.Event)
		close(sw.Error)
	}()

	return sw, nil
}

func (w *SyncWatcher) Close() error {
	// We close the fsnotify watcher.
	// That will close our incoming channels, and so close the SyncWatcher
	// indirectly.
	err := w.watcher.Close()
	if err != nil {
		return err
	}
	return nil
}

// This is like RemoveWatch except that it does not unwatch the root directory.
func (w *SyncWatcher) removeWatch(path string) error {
	w.pathMutex.Lock()
	defer w.pathMutex.Unlock()

	// Recursively remove all the watches from the given directory, and its
	// subdirectories. The root directory will not be unwatched (RemoveWatch
	// takes care of that).
	var recursive_remove func(dir string) error
	recursive_remove = func(dir string) error {
		children, ok := w.paths[dir]
		if ok {
			for _, child := range strings.Split(children, "\000") {
				if len(child) > 0 {
					// deliberately ignore errors from child watches
					recursive_remove(filepath.Join(dir, child))
				}
			}
			if _, isroot := w.roots[dir]; !isroot {
				delete(w.paths, dir)
				return w.watcher.Remove(dir)
			}
		}
		return errors.New("cannot remove uknown watch: " + dir)
	}

	return recursive_remove(path)
}

func (w *SyncWatcher) RemoveWatch(path string) error {
	// We want to unwatch the whole tree, including to root.
	// If we unregister the root then removeWatch will take care of the rest.
	w.pathMutex.Lock()
	if _, isroot := w.roots[path]; isroot {
		delete(w.roots, path)
	}
	w.pathMutex.Unlock()
	return w.removeWatch(path)
}

func (w *SyncWatcher) watch(path string) error {
	w.pathMutex.Lock()
	defer w.pathMutex.Unlock()

	return filepath.Walk(path, func(p string, info os.FileInfo, err error) error {
		if err == nil && info.IsDir() {
			if shouldIgnore(w.mainPath, w.ignorePaths, w.ignorePatterns, p) {
				return err
			}
			err = w.watcher.Add(p)
			if err == nil {
				w.paths[p] = ""
				parent := filepath.Dir(p)
				if _, ok := w.paths[parent]; ok {
					// Record the directory structure so that it can be
					// walked again when we need to remove the watches.
					w.paths[parent] += filepath.Base(p) + "\000"
				}
			}
		}
		return err
	})
}

func (w *SyncWatcher) Watch(path string) error {
	w.pathMutex.Lock()
	_, present := w.paths[path]

	if present {
		w.pathMutex.Unlock()
		return errors.New("cannot watch path twice: " + path)
	}
	w.roots[path] = 1
	w.pathMutex.Unlock()

	return w.watch(path)
}

func (w *SyncWatcher) String() string {
	w.pathMutex.Lock()
	defer w.pathMutex.Unlock()

	str := "SyncWatch:"
	for path := range w.paths {
		str += " " + path + " \"" + w.paths[path] + "\""
	}
	return str
}
<|endoftext|>


<commit_before>package mudlib

import (
	"fmt"
	"io"
	"log"
	"sort"
	"strings"
)

type command struct {
	name             string
	minArgs, maxArgs int
	usage            []string
	do               func(client, []string) (*string, *message)
}

var commands = make(map[string]command)

func init() {
	commands["quit"] = command{
		minArgs: 0,
		maxArgs: 0,
		usage:   []string{""},
		do: func(cl client, args []string) (*string, *message) {
			io.WriteString(cl.conn, "Bye!\n")
			cl.conn.Close()
			return nil, &message{
				from:        cl,
				message:     "",
				messageType: messageTypeQuit,
			}
		},
	}
	commands["say"] = command{
		minArgs: 1,
		maxArgs: -1,
		usage:   []string{"<message>"},
		do: func(cl client, args []string) (*string, *message) {
			return nil, &message{
				message:     strings.Join(args, " "),
				messageType: messageTypeSay,
			}
		},
	}
	commands["tell"] = command{
		minArgs: 2,
		maxArgs: -1,
		usage:   []string{"<player> <message>"},
		do: func(cl client, args []string) (*string, *message) {
			player, err := players.get(args[0])
			if err != nil {
				ret := fmt.Sprintf("Can't find player %q\n", args[0])
				return &ret, nil
			}
			if conn, _ := player.isConnected(); conn {
				return nil, &message{
					to:          player.Nickname,
					message:     strings.Join(args[1:], " "),
					messageType: messageTypeTell,
				}
			}
			ret := fmt.Sprintf("%q is not online.\n", args[0])
			return &ret, nil
		},
	}
	commands["me"] = command{
		minArgs: 1,
		maxArgs: -1,
		usage:   []string{"<emotes>"},
		do: func(cl client, args []string) (*string, *message) {
			return nil, &message{
				message:     strings.Join(args, " "),
				messageType: messageTypeEmote,
			}
		},
	}
	commands["shout"] = command{
		minArgs: 1,
		maxArgs: -1,
		usage:   []string{"<message>"},
		do: func(cl client, args []string) (*string, *message) {
			return nil, &message{
				message:     strings.Join(args, " "),
				messageType: messageTypeShout,
			}
		},
	}
	commands["who"] = command{
		minArgs: 0,
		maxArgs: 0,
		usage:   []string{""},
		do: func(cl client, args []string) (*string, *message) {
			ret := setFg(colorWhite, fmt.Sprintf("%v\n", getConnected()))
			return &ret, nil
		},
	}
	commands["finger"] = command{
		minArgs: 1,
		maxArgs: 1,
		usage:   []string{"<player>"},
		do: func(cl client, args []string) (*string, *message) {
			toPrint := ""
			if player, err := players.get(args[0]); err == nil {
				toPrint = setFg(colorWhite, fmt.Sprintf("%+v ", player.finger()))
				if c, _ := player.isConnected(); c {
					toPrint += setFgBold(colorGreen, "[online]\n")
				} else {
					toPrint += setFgBold(colorRed, "[offline]\n")
				}
			} else {
				toPrint = fmt.Sprintf("Failed to find player %q.\n", args[0])
			}
			return &toPrint, nil
		},
	}
	commands["look"] = command{
		minArgs: 0,
		maxArgs: -1,
		usage:   []string{"", "<object>", "<player>"},
		do: func(cl client, args []string) (*string, *message) {
			switch len(args) {
			case 0:
				// room look
				if room, err := rooms.get(cl.player.Room); err == nil {
					desc := room.describe()
					return &desc, nil
				}
				// TODO: handle limbo
				log.Printf("%+v in limbo.\n", cl.player)
				desc := "You're in limbo.\n"
				return &desc, nil
			default:
				// TODO: look at objects/players
				return nil, nil
			}
		},
	}
	commands["help"] = command{
		minArgs: 0,
		maxArgs: 1,
		usage:   []string{"", "<command>"},
		do: func(cl client, args []string) (*string, *message) {
			switch len(args) {
			case 0:
				ret := fmt.Sprintf("Available commands:\n")
				keys := []string{}
				for k := range commands {
					keys = append(keys, k)
				}
				sort.Strings(keys)
				ret += fmt.Sprintf("  %s\n", strings.Join(keys, ", "))
				return &ret, nil
			case 1:
				if c, ok := commands[args[0]]; ok {
					// Use abstracted usage print method
					ret := c.printUsage(args[0])
					return &ret, nil
				}
				ret := fmt.Sprintf("Unknown command %q.\n", args[0])
				return &ret, nil
			}
			return nil, nil
		},
	}
}

func (c command) printUsage(cmd string) string {
	usage := "Usage:\n"
	for _, s := range c.usage {
		usage += fmt.Sprintf("  /%s %s\n", cmd, s)
	}
	return usage
}

func doCommand(cl client, ch chan<- message, cmd string, args []string) error {
	if c, ok := commands[cmd[1:]]; ok {
		if (c.minArgs != -1 && len(args) < c.minArgs) || (c.maxArgs != -1 && len(args) > c.maxArgs) {
			io.WriteString(cl.conn, c.printUsage(cmd))
			return nil
		}
		toPrint, msg := c.do(cl, args)
		if toPrint != nil {
			io.WriteString(cl.conn, *toPrint)
		}
		if msg != nil {
			msg.from = cl
			ch <- *msg
		}
		return nil
	}
	io.WriteString(cl.conn, "What? (try \"/help\")\n")
	return fmt.Errorf("Failed to find command %q", cmd)
}
<commit_msg>Make a better 'who'<commit_after>package mudlib

import (
	"fmt"
	"io"
	"log"
	"sort"
	"strings"
)

type command struct {
	name             string
	minArgs, maxArgs int
	usage            []string
	do               func(client, []string) (*string, *message)
}

var commands = make(map[string]command)

func init() {
	commands["quit"] = command{
		minArgs: 0,
		maxArgs: 0,
		usage:   []string{""},
		do: func(cl client, args []string) (*string, *message) {
			io.WriteString(cl.conn, "Bye!\n")
			cl.conn.Close()
			return nil, &message{
				from:        cl,
				message:     "",
				messageType: messageTypeQuit,
			}
		},
	}
	commands["say"] = command{
		minArgs: 1,
		maxArgs: -1,
		usage:   []string{"<message>"},
		do: func(cl client, args []string) (*string, *message) {
			return nil, &message{
				message:     strings.Join(args, " "),
				messageType: messageTypeSay,
			}
		},
	}
	commands["tell"] = command{
		minArgs: 2,
		maxArgs: -1,
		usage:   []string{"<player> <message>"},
		do: func(cl client, args []string) (*string, *message) {
			player, err := players.get(args[0])
			if err != nil {
				ret := fmt.Sprintf("Can't find player %q\n", args[0])
				return &ret, nil
			}
			if conn, _ := player.isConnected(); conn {
				return nil, &message{
					to:          player.Nickname,
					message:     strings.Join(args[1:], " "),
					messageType: messageTypeTell,
				}
			}
			ret := fmt.Sprintf("%q is not online.\n", args[0])
			return &ret, nil
		},
	}
	commands["me"] = command{
		minArgs: 1,
		maxArgs: -1,
		usage:   []string{"<emotes>"},
		do: func(cl client, args []string) (*string, *message) {
			return nil, &message{
				message:     strings.Join(args, " "),
				messageType: messageTypeEmote,
			}
		},
	}
	commands["shout"] = command{
		minArgs: 1,
		maxArgs: -1,
		usage:   []string{"<message>"},
		do: func(cl client, args []string) (*string, *message) {
			return nil, &message{
				message:     strings.Join(args, " "),
				messageType: messageTypeShout,
			}
		},
	}
	commands["who"] = command{
		minArgs: 0,
		maxArgs: 0,
		usage:   []string{""},
		do: func(cl client, args []string) (*string, *message) {
      connected := getConnected()
      ret := fmt.Sprintf("There are currently %d players connected:\n  %s\n", len(connected), strings.Join(getConnected(), ", "))
			return &ret, nil
		},
	}
	commands["finger"] = command{
		minArgs: 1,
		maxArgs: 1,
		usage:   []string{"<player>"},
		do: func(cl client, args []string) (*string, *message) {
			toPrint := ""
			if player, err := players.get(args[0]); err == nil {
				toPrint = setFg(colorWhite, fmt.Sprintf("%+v ", player.finger()))
				if c, _ := player.isConnected(); c {
					toPrint += setFgBold(colorGreen, "[online]\n")
				} else {
					toPrint += setFgBold(colorRed, "[offline]\n")
				}
			} else {
				toPrint = fmt.Sprintf("Failed to find player %q.\n", args[0])
			}
			return &toPrint, nil
		},
	}
	commands["look"] = command{
		minArgs: 0,
		maxArgs: -1,
		usage:   []string{"", "<object>", "<player>"},
		do: func(cl client, args []string) (*string, *message) {
			switch len(args) {
			case 0:
				// room look
				if room, err := rooms.get(cl.player.Room); err == nil {
					desc := room.describe()
					return &desc, nil
				}
				// TODO: handle limbo
				log.Printf("%+v in limbo.\n", cl.player)
				desc := "You're in limbo.\n"
				return &desc, nil
			default:
				// TODO: look at objects/players
				return nil, nil
			}
		},
	}
	commands["help"] = command{
		minArgs: 0,
		maxArgs: 1,
		usage:   []string{"", "<command>"},
		do: func(cl client, args []string) (*string, *message) {
			switch len(args) {
			case 0:
				ret := fmt.Sprintf("Available commands:\n")
				keys := []string{}
				for k := range commands {
					keys = append(keys, k)
				}
				sort.Strings(keys)
				ret += fmt.Sprintf("  %s\n", strings.Join(keys, ", "))
				return &ret, nil
			case 1:
				if c, ok := commands[args[0]]; ok {
					// Use abstracted usage print method
					ret := c.printUsage(args[0])
					return &ret, nil
				}
				ret := fmt.Sprintf("Unknown command %q.\n", args[0])
				return &ret, nil
			}
			return nil, nil
		},
	}
}

func (c command) printUsage(cmd string) string {
	usage := "Usage:\n"
	for _, s := range c.usage {
		usage += fmt.Sprintf("  /%s %s\n", cmd, s)
	}
	return usage
}

func doCommand(cl client, ch chan<- message, cmd string, args []string) error {
	if c, ok := commands[cmd[1:]]; ok {
		if (c.minArgs != -1 && len(args) < c.minArgs) || (c.maxArgs != -1 && len(args) > c.maxArgs) {
			io.WriteString(cl.conn, c.printUsage(cmd))
			return nil
		}
		toPrint, msg := c.do(cl, args)
		if toPrint != nil {
			io.WriteString(cl.conn, *toPrint)
		}
		if msg != nil {
			msg.from = cl
			ch <- *msg
		}
		return nil
	}
	io.WriteString(cl.conn, "What? (try \"/help\")\n")
	return fmt.Errorf("Failed to find command %q", cmd)
}
<|endoftext|>


<commit_before>package main

import (
	"./router"
	"github.com/iris-contrib/middleware/logger"
	"github.com/kataras/go-template/html"
	"github.com/kataras/iris"
)

func main() {

	// set the isDevelopment
	iris.Config.IsDevelopment=true

	// set the template engine
	iris.UseTemplate(html.New(html.Config{Layout: "layout.html"})).Directory("e:/mygithub/gowork/src/myProject/templates", ".html")
	// set static folder(s)

	iris.StaticWeb("/public", "e:/mygithub/gowork/src/myProject/static", 1)

	// set the custom errors
	iris.OnError(iris.StatusNotFound, func(ctx *iris.Context) {
		ctx.Render("errors/404.html", iris.Map{"Title": iris.StatusText(iris.StatusNotFound)})
	})

	iris.OnError(iris.StatusInternalServerError, func(ctx *iris.Context) {
		ctx.Render("errors/500.html", nil, iris.RenderOptions{"layout": iris.NoLayout})
	})

	// set the global middlewares
	iris.Use(logger.New())
	// register the routes & the public API
	registerRoutes()

	iris.Listen(":8080")
}

func registerRoutes() {
	// register index using a 'Handler'
	iris.Handle("GET", "/", router.Index())

	// this is other way to declare a route
	// using a 'HandlerFunc'
	iris.Get("/about", router.About)

	// Dynamic route

	iris.Get("/profile/:username", router.Profile)("user-profile")
	// user-profile is the custom,optional, route's Name: with this we can use the {{ url "user-profile" $username}} inside userlist.html

	// iris.Get("/all", routes.UserList)
}
<commit_msg>mac 配置<commit_after>package main

import (
	"./router"
	"github.com/iris-contrib/middleware/logger"
	"github.com/kataras/go-template/html"
	"github.com/kataras/iris"
)

func main() {

	// set the isDevelopment
	iris.Config.IsDevelopment = true

	// set the template engine
	iris.UseTemplate(html.New(html.Config{Layout: "layout.html"})).Directory("./templates", ".html")
	// set static folder(s)

	iris.StaticWeb("/public", "./static", 1)

	// set the custom errors
	iris.OnError(iris.StatusNotFound, func(ctx *iris.Context) {
		ctx.Render("errors/404.html", iris.Map{"Title": iris.StatusText(iris.StatusNotFound)})
	})

	iris.OnError(iris.StatusInternalServerError, func(ctx *iris.Context) {
		ctx.Render("errors/500.html", nil, iris.RenderOptions{"layout": iris.NoLayout})
	})

	// set the global middlewares
	iris.Use(logger.New())
	// register the routes & the public API
	registerRoutes()

	iris.Listen(":8080")
}

func registerRoutes() {
	// register index using a 'Handler'
	iris.Handle("GET", "/", router.Index())

	// this is other way to declare a route
	// using a 'HandlerFunc'
	iris.Get("/about", router.About)

	// Dynamic route

	iris.Get("/profile/:username", router.Profile)("user-profile")
	// user-profile is the custom,optional, route's Name: with this we can use the {{ url "user-profile" $username}} inside userlist.html

	// iris.Get("/all", routes.UserList)
}
<|endoftext|>


<commit_before>package json

import (
	"github.com/anchore/syft/syft/distro"
	"github.com/anchore/syft/syft/pkg"
	"github.com/anchore/syft/syft/scope"
)

type Document struct {
	Artifacts []Artifact   `json:"artifacts"`
	Source    Source       `json:"source"`
	Distro    Distribution `json:"distro"`
}

// Distritbution provides information about a detected Linux Distribution
type Distribution struct {
	Name    string `json:"name"`
	Version string `json:"version"`
}

func NewDocument(catalog *pkg.Catalog, s scope.Scope, d distro.Distro) (Document, error) {
	doc := Document{
		Artifacts: make([]Artifact, 0),
	}

	src, err := NewSource(s)
	if err != nil {
		return Document{}, nil
	}
	doc.Source = src
	distroName := d.Name()
	if distroName == "UnknownDistroType" {
		distroName = ""
	}
	doc.Distro = Distribution{
		Name:    distroName,
		Version: d.FullVersion(),
	}

	for _, p := range catalog.Sorted() {
		art, err := NewArtifact(p, s)
		if err != nil {
			return Document{}, err
		}
		doc.Artifacts = append(doc.Artifacts, art)
	}

	return doc, nil
}
<commit_msg>presenter: provide ID_LIKE information in json<commit_after>package json

import (
	"github.com/anchore/syft/syft/distro"
	"github.com/anchore/syft/syft/pkg"
	"github.com/anchore/syft/syft/scope"
)

type Document struct {
	Artifacts []Artifact   `json:"artifacts"`
	Source    Source       `json:"source"`
	Distro    Distribution `json:"distro"`
}

// Distritbution provides information about a detected Linux Distribution
type Distribution struct {
	Name    string `json:"name"`
	Version string `json:"version"`
	IDLike  string `json:"idLike"`
}

func NewDocument(catalog *pkg.Catalog, s scope.Scope, d distro.Distro) (Document, error) {
	doc := Document{
		Artifacts: make([]Artifact, 0),
	}

	src, err := NewSource(s)
	if err != nil {
		return Document{}, nil
	}
	doc.Source = src
	distroName := d.Name()
	if distroName == "UnknownDistroType" {
		distroName = ""
	}
	doc.Distro = Distribution{
		Name:    distroName,
		Version: d.FullVersion(),
		IDLike:  d.IDLike,
	}

	for _, p := range catalog.Sorted() {
		art, err := NewArtifact(p, s)
		if err != nil {
			return Document{}, err
		}
		doc.Artifacts = append(doc.Artifacts, art)
	}

	return doc, nil
}
<|endoftext|>