<commit_before>// Copyright © 2013 Steve Francia <spf@spf13.com>.
//
// 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.

// Commands similar to git, go tools and other modern CLI tools
// inspired by go, go-Commander, gh and subcommand

package cobra

import (
	"bytes"
	"fmt"
	flag "github.com/spf13/pflag"
	"io"
	"strings"
)

// Command is just that, a command for your application.
// eg.  'go run' ... 'run' is the command. Cobra requires
// you to define the usage and description as part of your command
// definition to ensure usability.
type Command struct {
	// Name is the command name, usually the executable's name.
	name string
	// The one-line usage message.
	Use string
	// The short description shown in the 'help' output.
	Short string
	// The long message shown in the 'help <this-command>' output.
	Long string
	// Set of flags specific to this command.
	flags *flag.FlagSet
	// Set of flags children commands will inherit
	pflags *flag.FlagSet
	// Run runs the command.
	// The args are the arguments after the command name.
	Run func(cmd *Command, args []string)
	// Commands is the list of commands supported by this Commander program.
	commands []*Command
	// Parent Command for this command
	parent       *Command
	cmdr         *Commander
	flagErrorBuf *bytes.Buffer
}

// find the target command given the args and command tree
// Meant to be run on the highest node. Only searches down.
func (c *Command) Find(args []string) (cmd *Command, a []string, err error) {
	if c == nil {
		return nil, nil, fmt.Errorf("Called find() on a nil Command")
	}

	validSubCommand := false
	if len(args) > 0 && c.HasSubCommands() {
		for _, cmd := range c.commands {
			if cmd.Name() == args[0] {
				validSubCommand = true
				return cmd.Find(args[1:])
			}
		}
	}
	if !validSubCommand && c.Runnable() {
		return c, args, nil
	}

	return nil, nil, nil
}

func (c *Command) Root() *Command {
	var findRoot func(*Command) *Command

	findRoot = func(x *Command) *Command {
		if x.HasParent() {
			return findRoot(x.parent)
		} else {
			return x
		}
	}

	return findRoot(c)
}

func (c *Command) Commander() *Commander {
	cmdr := c.Root()
	if cmdr.cmdr != nil {
		return cmdr.cmdr
	} else {
		panic("commander not found")
	}
}

func (c *Command) Out() io.Writer {
	return c.Commander().out()
}

// execute the command determined by args and the command tree
func (c *Command) execute(args []string) (err error) {
	err = fmt.Errorf("unknown subcommand %q\nRun 'help' for usage.\n", args[0])

	if c == nil {
		return fmt.Errorf("Called Execute() on a nil Command")
	}

	cmd, a, e := c.Find(args)
	if e == nil {
		err = cmd.ParseFlags(a)
		if err != nil {
			// report flag parsing error
			c.Println(strings.Split(err.Error(), "\n")[0])
			erx := cmd.Usage()
			if erx != nil {
				return erx
			}
			return err
		} else {
			argWoFlags := cmd.Flags().Args()
			cmd.Run(cmd, argWoFlags)
			return nil
		}
	}
	err = e
	return err
}

// Used for testing
func (c *Command) ResetCommands() {
	c.commands = nil
}

func (c *Command) Commands() []*Command {
	return c.commands
}

// Add one or many commands as children of this
func (c *Command) AddCommand(cmds ...*Command) {
	for i, x := range cmds {
		if cmds[i] == c {
			panic("Command can't be a child of itself")
		}
		cmds[i].parent = c
		c.commands = append(c.commands, x)
	}
}

// Convenience method to Print to the defined output
func (c *Command) Print(i ...interface{}) {
	c.Commander().PrintOut(i...)
}

// Convenience method to Println to the defined output
func (c *Command) Println(i ...interface{}) {
	str := fmt.Sprintln(i...)
	c.Print(str)
}

// Convenience method to Printf to the defined output
func (c *Command) Printf(format string, i ...interface{}) {
	str := fmt.Sprintf(format, i...)
	c.Print(str)
}

// Output the usage for the command
// Used when a user provides invalid input
// Can be defined by user by overriding Commander.UsageFunc
func (c *Command) Usage() error {
	err := c.Commander().UsageFunc(c)
	return err
}

func (c *Command) UsageString() string {
	tmpOutput := c.Commander().cmdr.output
	bb := new(bytes.Buffer)
	c.Commander().SetOutput(bb)
	c.Usage()
	c.Commander().cmdr.output = tmpOutput
	return bb.String()
}

// The full path to this command
func (c *Command) CommandPath() string {
	str := c.Name()
	x := c
	for x.HasParent() {
		str = x.parent.Name() + " " + str
		x = x.parent
	}
	return str
}

//The full usage for a given command (including parents)
func (c *Command) UseLine() string {
	str := ""
	if c.HasParent() {
		str = c.parent.CommandPath() + " "
	}
	return str + c.Use
}

// For use in determining which flags have been assigned to which commands
// and which persist
func (c *Command) DebugFlags() {
	c.Println("DebugFlags called on", c.Name())
	var debugflags func(*Command)

	debugflags = func(x *Command) {
		if x.HasFlags() || x.HasPersistentFlags() {
			c.Println(x.Name())
		}
		if x.HasFlags() {
			x.flags.VisitAll(func(f *flag.Flag) {
				if x.HasPersistentFlags() {
					if x.persistentFlag(f.Name) == nil {
						c.Println("  -"+f.Shorthand+",", "--"+f.Name, "["+f.DefValue+"]", "", f.Value, "  [L]")
					} else {
						c.Println("  -"+f.Shorthand+",", "--"+f.Name, "["+f.DefValue+"]", "", f.Value, "  [LP]")
					}
				} else {
					c.Println("  -"+f.Shorthand+",", "--"+f.Name, "["+f.DefValue+"]", "", f.Value, "  [L]")
				}
			})
		}
		if x.HasPersistentFlags() {
			x.pflags.VisitAll(func(f *flag.Flag) {
				if x.HasFlags() {
					if x.flags.Lookup(f.Name) == nil {
						c.Println("  -"+f.Shorthand+",", "--"+f.Name, "["+f.DefValue+"]", "", f.Value, "  [P]")
					}
				} else {
					c.Println("  -"+f.Shorthand+",", "--"+f.Name, "["+f.DefValue+"]", "", f.Value, "  [P]")
				}
			})
		}
		c.Println(x.flagErrorBuf)
		if x.HasSubCommands() {
			for _, y := range x.commands {
				debugflags(y)
			}
		}
	}

	debugflags(c)
}

// Name returns the command's name: the first word in the use line.
func (c *Command) Name() string {
	if c.name != "" {
		return c.name
	}
	name := c.Use
	i := strings.Index(name, " ")
	if i >= 0 {
		name = name[:i]
	}
	return name
}

// Determine if the command is itself runnable
func (c *Command) Runnable() bool {
	return c.Run != nil
}

// Determine if the command has children commands
func (c *Command) HasSubCommands() bool {
	return len(c.commands) > 0
}

// Determine if the command is a child command
func (c *Command) HasParent() bool {
	return c.parent != nil
}

// Get the Commands FlagSet
func (c *Command) Flags() *flag.FlagSet {
	if c.flags == nil {
		c.flags = flag.NewFlagSet(c.Name(), flag.ContinueOnError)
		if c.flagErrorBuf == nil {
			c.flagErrorBuf = new(bytes.Buffer)
		}
		c.flags.SetOutput(c.flagErrorBuf)
	}
	return c.flags
}

// Get the Commands Persistent FlagSet
func (c *Command) PersistentFlags() *flag.FlagSet {
	if c.pflags == nil {
		c.pflags = flag.NewFlagSet(c.Name(), flag.ContinueOnError)
		if c.flagErrorBuf == nil {
			c.flagErrorBuf = new(bytes.Buffer)
		}
		c.pflags.SetOutput(c.flagErrorBuf)
	}
	return c.pflags
}

// For use in testing
func (c *Command) ResetFlags() {
	c.flagErrorBuf = new(bytes.Buffer)
	c.flagErrorBuf.Reset()
	c.flags = flag.NewFlagSet(c.Name(), flag.ContinueOnError)
	c.flags.SetOutput(c.flagErrorBuf)
	c.pflags = flag.NewFlagSet(c.Name(), flag.ContinueOnError)
	c.pflags.SetOutput(c.flagErrorBuf)
}

// Does the command contain flags (local not persistent)
func (c *Command) HasFlags() bool {
	return c.Flags().HasFlags()
}

// Does the command contain persistent flags
func (c *Command) HasPersistentFlags() bool {
	return c.PersistentFlags().HasFlags()
}

// Climbs up the command tree looking for matching flag
func (c *Command) Flag(name string) (flag *flag.Flag) {
	flag = c.Flags().Lookup(name)

	if flag == nil {
		flag = c.persistentFlag(name)
	}

	return
}

// recursively find matching persistent flag
func (c *Command) persistentFlag(name string) (flag *flag.Flag) {
	if c.HasPersistentFlags() {
		flag = c.PersistentFlags().Lookup(name)
	}

	if flag == nil && c.HasParent() {
		flag = c.parent.persistentFlag(name)
	}
	return
}

// Parses persistent flag tree & local flags
func (c *Command) ParseFlags(args []string) (err error) {
	c.mergePersistentFlags()

	err = c.Flags().Parse(args)
	if err != nil {
		return err
	}
	// The upstream library adds spaces to the error
	// response regardless of success.
	// Handling it here until fixing upstream
	if len(strings.TrimSpace(c.flagErrorBuf.String())) > 1 {
		return fmt.Errorf("%s", c.flagErrorBuf.String())
	}
	return nil
}

func (c *Command) mergePersistentFlags() {
	var rmerge func(x *Command)

	rmerge = func(x *Command) {
		if x.HasPersistentFlags() {
			x.PersistentFlags().VisitAll(func(f *flag.Flag) {
				if c.Flags().Lookup(f.Name) == nil {
					c.Flags().AddFlag(f)
				}
			})
		}
		if x.HasParent() {
			rmerge(x.parent)
		}
	}

	rmerge(c)
}

func (c *Command) Parent() *Command {
	return c.parent
}
<commit_msg>Add ability to cast a command to a commander<commit_after>// Copyright © 2013 Steve Francia <spf@spf13.com>.
//
// 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.

// Commands similar to git, go tools and other modern CLI tools
// inspired by go, go-Commander, gh and subcommand

package cobra

import (
	"bytes"
	"fmt"
	flag "github.com/spf13/pflag"
	"io"
	"strings"
)

// Command is just that, a command for your application.
// eg.  'go run' ... 'run' is the command. Cobra requires
// you to define the usage and description as part of your command
// definition to ensure usability.
type Command struct {
	// Name is the command name, usually the executable's name.
	name string
	// The one-line usage message.
	Use string
	// The short description shown in the 'help' output.
	Short string
	// The long message shown in the 'help <this-command>' output.
	Long string
	// Set of flags specific to this command.
	flags *flag.FlagSet
	// Set of flags children commands will inherit
	pflags *flag.FlagSet
	// Run runs the command.
	// The args are the arguments after the command name.
	Run func(cmd *Command, args []string)
	// Commands is the list of commands supported by this Commander program.
	commands []*Command
	// Parent Command for this command
	parent       *Command
	cmdr         *Commander
	flagErrorBuf *bytes.Buffer
}

// Convert a Command into an (initialized) Commander
func (cmd *Command) ToCommander() (c *Commander) {
	c = NewCommander()
	c.name = cmd.Name()
	c.Use = cmd.Use
	c.Short = cmd.Short
	c.Long = cmd.Long
	c.flags = cmd.flags
	c.pflags = cmd.pflags
	c.Run = cmd.Run
	c.commands = cmd.commands
	c.resetChildrensParents()
	c.flagErrorBuf = cmd.flagErrorBuf
	return
}

// Really only used when casting a command to a commander
func (c *Command) resetChildrensParents() {
	for _, x := range c.commands {
		x.parent = c
	}
}

// find the target command given the args and command tree
// Meant to be run on the highest node. Only searches down.
func (c *Command) Find(args []string) (cmd *Command, a []string, err error) {
	if c == nil {
		return nil, nil, fmt.Errorf("Called find() on a nil Command")
	}

	validSubCommand := false
	if len(args) > 0 && c.HasSubCommands() {
		for _, cmd := range c.commands {
			if cmd.Name() == args[0] {
				validSubCommand = true
				return cmd.Find(args[1:])
			}
		}
	}
	if !validSubCommand && c.Runnable() {
		return c, args, nil
	}

	return nil, nil, nil
}

func (c *Command) Root() *Command {
	var findRoot func(*Command) *Command

	findRoot = func(x *Command) *Command {
		if x.HasParent() {
			return findRoot(x.parent)
		} else {
			return x
		}
	}

	return findRoot(c)
}

func (c *Command) Commander() *Commander {
	cmdr := c.Root()
	if cmdr.cmdr != nil {
		return cmdr.cmdr
	} else {
		panic("commander not found")
	}
}

func (c *Command) Out() io.Writer {
	return c.Commander().out()
}

// execute the command determined by args and the command tree
func (c *Command) execute(args []string) (err error) {
	err = fmt.Errorf("unknown subcommand %q\nRun 'help' for usage.\n", args[0])

	if c == nil {
		return fmt.Errorf("Called Execute() on a nil Command")
	}

	cmd, a, e := c.Find(args)
	if e == nil {
		err = cmd.ParseFlags(a)
		if err != nil {
			// report flag parsing error
			c.Println(strings.Split(err.Error(), "\n")[0])
			erx := cmd.Usage()
			if erx != nil {
				return erx
			}
			return err
		} else {
			argWoFlags := cmd.Flags().Args()
			cmd.Run(cmd, argWoFlags)
			return nil
		}
	}
	err = e
	return err
}

// Used for testing
func (c *Command) ResetCommands() {
	c.commands = nil
}

func (c *Command) Commands() []*Command {
	return c.commands
}

// Add one or many commands as children of this
func (c *Command) AddCommand(cmds ...*Command) {
	for i, x := range cmds {
		if cmds[i] == c {
			panic("Command can't be a child of itself")
		}
		cmds[i].parent = c
		c.commands = append(c.commands, x)
	}
}

// Convenience method to Print to the defined output
func (c *Command) Print(i ...interface{}) {
	c.Commander().PrintOut(i...)
}

// Convenience method to Println to the defined output
func (c *Command) Println(i ...interface{}) {
	str := fmt.Sprintln(i...)
	c.Print(str)
}

// Convenience method to Printf to the defined output
func (c *Command) Printf(format string, i ...interface{}) {
	str := fmt.Sprintf(format, i...)
	c.Print(str)
}

// Output the usage for the command
// Used when a user provides invalid input
// Can be defined by user by overriding Commander.UsageFunc
func (c *Command) Usage() error {
	err := c.Commander().UsageFunc(c)
	return err
}

func (c *Command) UsageString() string {
	tmpOutput := c.Commander().cmdr.output
	bb := new(bytes.Buffer)
	c.Commander().SetOutput(bb)
	c.Usage()
	c.Commander().cmdr.output = tmpOutput
	return bb.String()
}

// The full path to this command
func (c *Command) CommandPath() string {
	str := c.Name()
	x := c
	for x.HasParent() {
		str = x.parent.Name() + " " + str
		x = x.parent
	}
	return str
}

//The full usage for a given command (including parents)
func (c *Command) UseLine() string {
	str := ""
	if c.HasParent() {
		str = c.parent.CommandPath() + " "
	}
	return str + c.Use
}

// For use in determining which flags have been assigned to which commands
// and which persist
func (c *Command) DebugFlags() {
	c.Println("DebugFlags called on", c.Name())
	var debugflags func(*Command)

	debugflags = func(x *Command) {
		if x.HasFlags() || x.HasPersistentFlags() {
			c.Println(x.Name())
		}
		if x.HasFlags() {
			x.flags.VisitAll(func(f *flag.Flag) {
				if x.HasPersistentFlags() {
					if x.persistentFlag(f.Name) == nil {
						c.Println("  -"+f.Shorthand+",", "--"+f.Name, "["+f.DefValue+"]", "", f.Value, "  [L]")
					} else {
						c.Println("  -"+f.Shorthand+",", "--"+f.Name, "["+f.DefValue+"]", "", f.Value, "  [LP]")
					}
				} else {
					c.Println("  -"+f.Shorthand+",", "--"+f.Name, "["+f.DefValue+"]", "", f.Value, "  [L]")
				}
			})
		}
		if x.HasPersistentFlags() {
			x.pflags.VisitAll(func(f *flag.Flag) {
				if x.HasFlags() {
					if x.flags.Lookup(f.Name) == nil {
						c.Println("  -"+f.Shorthand+",", "--"+f.Name, "["+f.DefValue+"]", "", f.Value, "  [P]")
					}
				} else {
					c.Println("  -"+f.Shorthand+",", "--"+f.Name, "["+f.DefValue+"]", "", f.Value, "  [P]")
				}
			})
		}
		c.Println(x.flagErrorBuf)
		if x.HasSubCommands() {
			for _, y := range x.commands {
				debugflags(y)
			}
		}
	}

	debugflags(c)
}

// Name returns the command's name: the first word in the use line.
func (c *Command) Name() string {
	if c.name != "" {
		return c.name
	}
	name := c.Use
	i := strings.Index(name, " ")
	if i >= 0 {
		name = name[:i]
	}
	return name
}

// Determine if the command is itself runnable
func (c *Command) Runnable() bool {
	return c.Run != nil
}

// Determine if the command has children commands
func (c *Command) HasSubCommands() bool {
	return len(c.commands) > 0
}

// Determine if the command is a child command
func (c *Command) HasParent() bool {
	return c.parent != nil
}

// Get the Commands FlagSet
func (c *Command) Flags() *flag.FlagSet {
	if c.flags == nil {
		c.flags = flag.NewFlagSet(c.Name(), flag.ContinueOnError)
		if c.flagErrorBuf == nil {
			c.flagErrorBuf = new(bytes.Buffer)
		}
		c.flags.SetOutput(c.flagErrorBuf)
	}
	return c.flags
}

// Get the Commands Persistent FlagSet
func (c *Command) PersistentFlags() *flag.FlagSet {
	if c.pflags == nil {
		c.pflags = flag.NewFlagSet(c.Name(), flag.ContinueOnError)
		if c.flagErrorBuf == nil {
			c.flagErrorBuf = new(bytes.Buffer)
		}
		c.pflags.SetOutput(c.flagErrorBuf)
	}
	return c.pflags
}

// For use in testing
func (c *Command) ResetFlags() {
	c.flagErrorBuf = new(bytes.Buffer)
	c.flagErrorBuf.Reset()
	c.flags = flag.NewFlagSet(c.Name(), flag.ContinueOnError)
	c.flags.SetOutput(c.flagErrorBuf)
	c.pflags = flag.NewFlagSet(c.Name(), flag.ContinueOnError)
	c.pflags.SetOutput(c.flagErrorBuf)
}

// Does the command contain flags (local not persistent)
func (c *Command) HasFlags() bool {
	return c.Flags().HasFlags()
}

// Does the command contain persistent flags
func (c *Command) HasPersistentFlags() bool {
	return c.PersistentFlags().HasFlags()
}

// Climbs up the command tree looking for matching flag
func (c *Command) Flag(name string) (flag *flag.Flag) {
	flag = c.Flags().Lookup(name)

	if flag == nil {
		flag = c.persistentFlag(name)
	}

	return
}

// recursively find matching persistent flag
func (c *Command) persistentFlag(name string) (flag *flag.Flag) {
	if c.HasPersistentFlags() {
		flag = c.PersistentFlags().Lookup(name)
	}

	if flag == nil && c.HasParent() {
		flag = c.parent.persistentFlag(name)
	}
	return
}

// Parses persistent flag tree & local flags
func (c *Command) ParseFlags(args []string) (err error) {
	c.mergePersistentFlags()

	err = c.Flags().Parse(args)
	if err != nil {
		return err
	}
	// The upstream library adds spaces to the error
	// response regardless of success.
	// Handling it here until fixing upstream
	if len(strings.TrimSpace(c.flagErrorBuf.String())) > 1 {
		return fmt.Errorf("%s", c.flagErrorBuf.String())
	}
	return nil
}

func (c *Command) mergePersistentFlags() {
	var rmerge func(x *Command)

	rmerge = func(x *Command) {
		if x.HasPersistentFlags() {
			x.PersistentFlags().VisitAll(func(f *flag.Flag) {
				if c.Flags().Lookup(f.Name) == nil {
					c.Flags().AddFlag(f)
				}
			})
		}
		if x.HasParent() {
			rmerge(x.parent)
		}
	}

	rmerge(c)
}

func (c *Command) Parent() *Command {
	return c.parent
}
<|endoftext|>


<commit_before>// Copyright 2014-2015, Truveris Inc. All Rights Reserved.
// Use of this source code is governed by the ISC license in the LICENSE file.

package main

import (
	"encoding/json"
	"errors"
	"log"
	"os"

	"github.com/jessevdk/go-flags"
)

// Cmd is a singleton used to store the command-line parameters.
type CmdLine struct {
	ConfigFile string `short:"c" description:"Configuration file" default:"/etc/ygord.conf"`
}

// ChannelCfg represents a per-channel grouping of minions.
type ChannelCfg struct {
	Minions []string
}

// Config is a singleton used to store the file configuration.
type Config struct {
	// AWS Region to use for SQS access (e.g. us-east-1).
	AWSRegionCode string

	// AWS access key id
	AWSAccessKeyID string
	// AWS secret access key
	AWSSecretAccessKey string

	// All the configured channels. ygord will JOIN every single one of
	// them and will push commands to the configured associated minions.
	Channels map[string]ChannelCfg

	// Queue used by ygord to receive feedback from the minions.
	QueueName string

	// Hostname and port to use to connect to the IRC server.
	IRCServer string

	// Nickname of the bot. FIXME: this is not currently synchronized
	IRCNickname string

	// Try to send debug information to this channel in lieu of log file.
	AdminChannel string

	// Any chatter from these nicks will be dropped (other bots).
	Ignore []string

	// Where to find the alias file. Will use the local alias file found in
	// the current directory by default.
	AliasFilePath string

	// Where to find the web files (static folder).
	WebRoot string

	// Where to find the minions file.
	MinionsFilePath string

	// If defined, start a web server to list the aliases (e.g. :8989)
	HTTPServerAddress string

	// If defined, it enables the "say" command and converts sentences into
	// streamable sound bites via a minion-accessible sayd.
	SaydURL string
}

// GetAutoJoinChannels returns a list of all the auto-join channels (all unique
// configured channels and debug channels).
func (cfg *Config) GetAutoJoinChannels() []string {
	channels := make(StringSet, 0)

	for name := range cfg.Channels {
		channels.Add(name)
	}

	if cfg.AdminChannel != "" {
		channels.Add(cfg.AdminChannel)
	}

	return channels.Array()
}

// GetChannelsByMinionName returns a list of channels given a minion name.
func (cfg *Config) GetChannelsByMinion(name string) []string {
	var channels []string

	for channelName, channelCfg := range cfg.Channels {
		for _, minionName := range channelCfg.Minions {
			if minionName == name {
				channels = append(channels, channelName)
				break
			}
		}
	}

	return channels
}

// GetChannelMinions returns all the minions configured for that channel.
func (cfg *Config) GetMinionsByChannel(channel string) []string {
	channelCfg, exists := cfg.Channels[channel]
	if !exists {
		log.Printf("error: %s has no queue(s) configured", channel)
		return nil
	}

	return channelCfg.Minions
}

// ParseConfigFile reads our JSON config file and validates its values, also
// populating defaults when possible.
func ParseConfigFile(cmd *CmdLine) (*Config, error) {
	cfg := &Config{}

	file, err := os.Open(cmd.ConfigFile)
	if err != nil {
		return nil, err
	}
	decoder := json.NewDecoder(file)
	err = decoder.Decode(&cfg)
	if err != nil {
		return nil, err
	}

	if cfg.QueueName == "" {
		return cfg, errors.New("'QueueName' is not defined")
	}

	if cfg.IRCServer == "" {
		return cfg, errors.New("'IRCServer' is not defined")
	}

	if cfg.IRCNickname == "" {
		return cfg, errors.New("'IRCNickname' is not defined")
	}

	if cfg.AWSRegionCode == "" {
		return cfg, errors.New("'AWSRegionCode' is not defined")
	}

	if cfg.AWSAccessKeyID == "" {
		return cfg, errors.New("'AWSAccessKeyID' is not defined")
	}

	if cfg.AWSSecretAccessKey == "" {
		return cfg, errors.New("'AWSSecretAccessKey' is not defined")
	}

	if cfg.AliasFilePath == "" {
		cfg.AliasFilePath = "aliases.cfg"
	}

	if cfg.MinionsFilePath == "" {
		cfg.MinionsFilePath = "minions.cfg"
	}

	// If a web server is started, make sure we configure a web root.
	if cfg.HTTPServerAddress != "" {
		if cfg.WebRoot == "" {
			return cfg, errors.New("'WebRoot' is not defined")
		}
	}

	return cfg, nil
}

// ParseCommandLine parses the command line arguments and populate the global
// cmd struct.
func ParseCommandLine() *CmdLine {
	cmd := &CmdLine{}
	flagParser := flags.NewParser(cmd, flags.PassDoubleDash)
	_, err := flagParser.Parse()
	if err != nil {
		println("command line error: " + err.Error())
		flagParser.WriteHelp(os.Stderr)
		os.Exit(1)
	}
	return cmd
}
<commit_msg>now grabs SoundCloudClientID from config file if it is present<commit_after>// Copyright 2014-2015, Truveris Inc. All Rights Reserved.
// Use of this source code is governed by the ISC license in the LICENSE file.

package main

import (
	"encoding/json"
	"errors"
	"log"
	"os"

	"github.com/jessevdk/go-flags"
)

// Cmd is a singleton used to store the command-line parameters.
type CmdLine struct {
	ConfigFile string `short:"c" description:"Configuration file" default:"/etc/ygord.conf"`
}

// ChannelCfg represents a per-channel grouping of minions.
type ChannelCfg struct {
	Minions []string
}

// Config is a singleton used to store the file configuration.
type Config struct {
	// AWS Region to use for SQS access (e.g. us-east-1).
	AWSRegionCode string

	// AWS access key id
	AWSAccessKeyID string
	// AWS secret access key
	AWSSecretAccessKey string

	// All the configured channels. ygord will JOIN every single one of
	// them and will push commands to the configured associated minions.
	Channels map[string]ChannelCfg

	// Queue used by ygord to receive feedback from the minions.
	QueueName string

	// Hostname and port to use to connect to the IRC server.
	IRCServer string

	// Nickname of the bot. FIXME: this is not currently synchronized
	IRCNickname string

	// Try to send debug information to this channel in lieu of log file.
	AdminChannel string

	// Any chatter from these nicks will be dropped (other bots).
	Ignore []string

	// Where to find the alias file. Will use the local alias file found in
	// the current directory by default.
	AliasFilePath string

	// Where to find the web files (static folder).
	WebRoot string

	// Where to find the minions file.
	MinionsFilePath string

	// If defined, start a web server to list the aliases (e.g. :8989)
	HTTPServerAddress string

	// If defined, it enables the "say" command and converts sentences into
	// streamable sound bites via a minion-accessible sayd.
	SaydURL string

	// If defined, it allows SoundCloud URLs to be resolved when passing URLs
	// to commands that utilize MediaObj.
	SoundCloudClientID string
}

// GetAutoJoinChannels returns a list of all the auto-join channels (all unique
// configured channels and debug channels).
func (cfg *Config) GetAutoJoinChannels() []string {
	channels := make(StringSet, 0)

	for name := range cfg.Channels {
		channels.Add(name)
	}

	if cfg.AdminChannel != "" {
		channels.Add(cfg.AdminChannel)
	}

	return channels.Array()
}

// GetChannelsByMinionName returns a list of channels given a minion name.
func (cfg *Config) GetChannelsByMinion(name string) []string {
	var channels []string

	for channelName, channelCfg := range cfg.Channels {
		for _, minionName := range channelCfg.Minions {
			if minionName == name {
				channels = append(channels, channelName)
				break
			}
		}
	}

	return channels
}

// GetChannelMinions returns all the minions configured for that channel.
func (cfg *Config) GetMinionsByChannel(channel string) []string {
	channelCfg, exists := cfg.Channels[channel]
	if !exists {
		log.Printf("error: %s has no queue(s) configured", channel)
		return nil
	}

	return channelCfg.Minions
}

// ParseConfigFile reads our JSON config file and validates its values, also
// populating defaults when possible.
func ParseConfigFile(cmd *CmdLine) (*Config, error) {
	cfg := &Config{}

	file, err := os.Open(cmd.ConfigFile)
	if err != nil {
		return nil, err
	}
	decoder := json.NewDecoder(file)
	err = decoder.Decode(&cfg)
	if err != nil {
		return nil, err
	}

	if cfg.QueueName == "" {
		return cfg, errors.New("'QueueName' is not defined")
	}

	if cfg.IRCServer == "" {
		return cfg, errors.New("'IRCServer' is not defined")
	}

	if cfg.IRCNickname == "" {
		return cfg, errors.New("'IRCNickname' is not defined")
	}

	if cfg.AWSRegionCode == "" {
		return cfg, errors.New("'AWSRegionCode' is not defined")
	}

	if cfg.AWSAccessKeyID == "" {
		return cfg, errors.New("'AWSAccessKeyID' is not defined")
	}

	if cfg.AWSSecretAccessKey == "" {
		return cfg, errors.New("'AWSSecretAccessKey' is not defined")
	}

	if cfg.AliasFilePath == "" {
		cfg.AliasFilePath = "aliases.cfg"
	}

	if cfg.MinionsFilePath == "" {
		cfg.MinionsFilePath = "minions.cfg"
	}

	// If a web server is started, make sure we configure a web root.
	if cfg.HTTPServerAddress != "" {
		if cfg.WebRoot == "" {
			return cfg, errors.New("'WebRoot' is not defined")
		}
	}

	return cfg, nil
}

// ParseCommandLine parses the command line arguments and populate the global
// cmd struct.
func ParseCommandLine() *CmdLine {
	cmd := &CmdLine{}
	flagParser := flags.NewParser(cmd, flags.PassDoubleDash)
	_, err := flagParser.Parse()
	if err != nil {
		println("command line error: " + err.Error())
		flagParser.WriteHelp(os.Stderr)
		os.Exit(1)
	}
	return cmd
}
<|endoftext|>


<commit_before>package docker

import (
	"github.com/Sirupsen/logrus"
	"github.com/docker/libcompose/docker"
	"github.com/docker/libcompose/project"
	dockerclient "github.com/fsouza/go-dockerclient"
	"github.com/rancher/os/config"
)

type Service struct {
	*docker.Service
	deps    map[string][]string
	context *docker.Context
	project *project.Project
}

func NewService(factory *ServiceFactory, name string, serviceConfig *project.ServiceConfig, context *docker.Context, project *project.Project) *Service {
	return &Service{
		Service: docker.NewService(name, serviceConfig, context),
		deps:    factory.Deps,
		context: context,
		project: project,
	}
}

func (s *Service) DependentServices() []project.ServiceRelationship {
	rels := s.Service.DependentServices()
	for _, dep := range s.deps[s.Name()] {
		rels = appendLink(rels, dep, true, s.project)
	}

	if s.requiresSyslog() {
		rels = appendLink(rels, "syslog", false, s.project)
	}

	if s.requiresUserDocker() {
		// Linking to cloud-init is a hack really.  The problem is we need to link to something
		// that will trigger a reload
		rels = appendLink(rels, "cloud-init", false, s.project)
	} else if s.missingImage() {
		rels = appendLink(rels, "network", false, s.project)
	}
	return rels
}

func (s *Service) missingImage() bool {
	image := s.Config().Image
	if image == "" {
		return false
	}
	client := s.context.ClientFactory.Create(s)
	i, err := client.InspectImage(s.Config().Image)
	return err != nil || i == nil
}

func (s *Service) requiresSyslog() bool {
	return s.Config().LogDriver == "syslog"
}

func (s *Service) requiresUserDocker() bool {
	return s.Config().Labels.MapParts()[config.SCOPE] != config.SYSTEM
}

func appendLink(deps []project.ServiceRelationship, name string, optional bool, p *project.Project) []project.ServiceRelationship {
	if _, ok := p.Configs[name]; !ok {
		return deps
	}
	rel := project.NewServiceRelationship(name, project.RelTypeLink)
	rel.Optional = optional
	return append(deps, rel)
}

func (s *Service) shouldRebuild() (bool, error) {
	containers, err := s.Containers()
	if err != nil {
		return false, err
	}
	for _, c := range containers {
		outOfSync, err := c.(*docker.Container).OutOfSync(s.Service.Config().Image)
		if err != nil {
			return false, err
		}

		_, containerInfo, err := s.getContainer()
		if containerInfo == nil || err != nil {
			return false, err
		}
		name := containerInfo.Name[1:]

		origRebuildLabel := containerInfo.Config.Labels[config.REBUILD]
		newRebuildLabel := s.Config().Labels.MapParts()[config.REBUILD]
		rebuildLabelChanged := newRebuildLabel != origRebuildLabel
		logrus.WithFields(logrus.Fields{
			"origRebuildLabel":    origRebuildLabel,
			"newRebuildLabel":     newRebuildLabel,
			"rebuildLabelChanged": rebuildLabelChanged,
			"outOfSync":           outOfSync}).Debug("Rebuild values")

		if origRebuildLabel == "always" || rebuildLabelChanged || origRebuildLabel != "false" && outOfSync {
			logrus.Infof("Rebuilding %s", name)
			return true, err
		} else if outOfSync {
			logrus.Warnf("%s needs rebuilding", name)
		}
	}
	return false, nil
}

func (s *Service) Up() error {
	labels := s.Config().Labels.MapParts()

	if err := s.Service.Create(); err != nil {
		return err
	}
	if labels[config.CREATE_ONLY] == "true" {
		return s.checkReload(labels)
	}
	shouldRebuild, err := s.shouldRebuild()
	if err != nil {
		return err
	}
	if shouldRebuild {
		cs, err := s.Service.Containers()
		if err != nil {
			return err
		}
		for _, c := range cs {
			if _, err := c.(*docker.Container).Recreate(s.Config().Image); err != nil {
				return err
			}
		}
	}
	if err := s.Service.Up(); err != nil {
		return err
	}
	if labels[config.DETACH] == "false" {
		if err := s.wait(); err != nil {
			return err
		}
	}

	return s.checkReload(labels)
}

func (s *Service) checkReload(labels map[string]string) error {
	if labels[config.RELOAD_CONFIG] == "true" {
		return project.ErrRestart
	}
	return nil
}

func (s *Service) Create() error {
	return s.Service.Create()
}

func (s *Service) getContainer() (*dockerclient.Client, *dockerclient.Container, error) {
	containers, err := s.Service.Containers()
	if err != nil {
		return nil, nil, err
	}

	if len(containers) == 0 {
		return nil, nil, nil
	}

	id, err := containers[0].ID()
	if err != nil {
		return nil, nil, err
	}

	client := s.context.ClientFactory.Create(s)
	info, err := client.InspectContainer(id)
	return client, info, err
}

func (s *Service) wait() error {
	client, info, err := s.getContainer()
	if err != nil || info == nil {
		return err
	}

	if _, err := client.WaitContainer(info.ID); err != nil {
		return err
	}

	return nil
}
<commit_msg>Recreate create-only containers and force rename after recreate<commit_after>package docker

import (
	"github.com/Sirupsen/logrus"
	"github.com/docker/libcompose/docker"
	"github.com/docker/libcompose/project"
	dockerclient "github.com/fsouza/go-dockerclient"
	"github.com/rancher/os/config"
)

type Service struct {
	*docker.Service
	deps    map[string][]string
	context *docker.Context
	project *project.Project
}

func NewService(factory *ServiceFactory, name string, serviceConfig *project.ServiceConfig, context *docker.Context, project *project.Project) *Service {
	return &Service{
		Service: docker.NewService(name, serviceConfig, context),
		deps:    factory.Deps,
		context: context,
		project: project,
	}
}

func (s *Service) DependentServices() []project.ServiceRelationship {
	rels := s.Service.DependentServices()
	for _, dep := range s.deps[s.Name()] {
		rels = appendLink(rels, dep, true, s.project)
	}

	if s.requiresSyslog() {
		rels = appendLink(rels, "syslog", false, s.project)
	}

	if s.requiresUserDocker() {
		// Linking to cloud-init is a hack really.  The problem is we need to link to something
		// that will trigger a reload
		rels = appendLink(rels, "cloud-init", false, s.project)
	} else if s.missingImage() {
		rels = appendLink(rels, "network", false, s.project)
	}
	return rels
}

func (s *Service) missingImage() bool {
	image := s.Config().Image
	if image == "" {
		return false
	}
	client := s.context.ClientFactory.Create(s)
	i, err := client.InspectImage(s.Config().Image)
	return err != nil || i == nil
}

func (s *Service) requiresSyslog() bool {
	return s.Config().LogDriver == "syslog"
}

func (s *Service) requiresUserDocker() bool {
	return s.Config().Labels.MapParts()[config.SCOPE] != config.SYSTEM
}

func appendLink(deps []project.ServiceRelationship, name string, optional bool, p *project.Project) []project.ServiceRelationship {
	if _, ok := p.Configs[name]; !ok {
		return deps
	}
	rel := project.NewServiceRelationship(name, project.RelTypeLink)
	rel.Optional = optional
	return append(deps, rel)
}

func (s *Service) shouldRebuild() (bool, error) {
	containers, err := s.Containers()
	if err != nil {
		return false, err
	}
	for _, c := range containers {
		outOfSync, err := c.(*docker.Container).OutOfSync(s.Service.Config().Image)
		if err != nil {
			return false, err
		}

		_, containerInfo, err := s.getContainer()
		if containerInfo == nil || err != nil {
			return false, err
		}
		name := containerInfo.Name[1:]

		origRebuildLabel := containerInfo.Config.Labels[config.REBUILD]
		newRebuildLabel := s.Config().Labels.MapParts()[config.REBUILD]
		rebuildLabelChanged := newRebuildLabel != origRebuildLabel
		logrus.WithFields(logrus.Fields{
			"origRebuildLabel":    origRebuildLabel,
			"newRebuildLabel":     newRebuildLabel,
			"rebuildLabelChanged": rebuildLabelChanged,
			"outOfSync":           outOfSync}).Debug("Rebuild values")

		if origRebuildLabel == "always" || rebuildLabelChanged || origRebuildLabel != "false" && outOfSync {
			logrus.Infof("Rebuilding %s", name)
			return true, err
		} else if outOfSync {
			logrus.Warnf("%s needs rebuilding", name)
		}
	}
	return false, nil
}

func (s *Service) Up() error {
	labels := s.Config().Labels.MapParts()

	if err := s.Service.Create(); err != nil {
		return err
	}
	shouldRebuild, err := s.shouldRebuild()
	if err != nil {
		return err
	}
	if shouldRebuild {
		cs, err := s.Service.Containers()
		if err != nil {
			return err
		}
		for _, c := range cs {
			if _, err := c.(*docker.Container).Recreate(s.Config().Image); err != nil {
				return err
			}
		}
		s.rename()
	}
	if labels[config.CREATE_ONLY] == "true" {
		return s.checkReload(labels)
	}
	if err := s.Service.Up(); err != nil {
		return err
	}
	if labels[config.DETACH] == "false" {
		if err := s.wait(); err != nil {
			return err
		}
	}

	return s.checkReload(labels)
}

func (s *Service) checkReload(labels map[string]string) error {
	if labels[config.RELOAD_CONFIG] == "true" {
		return project.ErrRestart
	}
	return nil
}

func (s *Service) Create() error {
	return s.Service.Create()
}

func (s *Service) getContainer() (*dockerclient.Client, *dockerclient.Container, error) {
	containers, err := s.Service.Containers()
	if err != nil {
		return nil, nil, err
	}

	if len(containers) == 0 {
		return nil, nil, nil
	}

	id, err := containers[0].ID()
	if err != nil {
		return nil, nil, err
	}

	client := s.context.ClientFactory.Create(s)
	info, err := client.InspectContainer(id)
	return client, info, err
}

func (s *Service) wait() error {
	client, info, err := s.getContainer()
	if err != nil || info == nil {
		return err
	}

	if _, err := client.WaitContainer(info.ID); err != nil {
		return err
	}

	return nil
}

func (s *Service) rename() error {
	client, info, err := s.getContainer()
	if err != nil || info == nil {
		return err
	}

	if len(info.Name) > 0 && info.Name[1:] != s.Name() {
		logrus.Debugf("Renaming container %s => %s", info.Name[1:], s.Name())
		return client.RenameContainer(dockerclient.RenameContainerOptions{ID: info.ID, Name: s.Name()})
	} else {
		return nil
	}
}
<|endoftext|>


<commit_before>package store

import "github.com/stampzilla/stampzilla-go/nodes/stampzilla-server/models"

func (store *Store) GetConnections() Connections {
	store.RLock()
	defer store.RUnlock()
	return store.Connections
}

func (store *Store) Connection(id string) *models.Connection {
	store.RLock()
	defer store.RUnlock()
	if conn, ok := store.Connections[id]; ok {
		return conn
	}
	return nil
}

func (store *Store) AddOrUpdateConnection(id string, c *models.Connection) {
	store.Lock()
	store.Connections[id] = c
	store.Unlock()

	store.runCallbacks("connections")
}

func (store *Store) ConnectionChanged() {
	store.runCallbacks("connections")
}

func (store *Store) RemoveConnection(id string) {
	store.Lock()
	delete(store.Connections, id)
	for _, device := range store.Devices.All() {
		if device.ID.Node == id {
			device.Online = false
		}
	}
	store.Unlock()

	store.runCallbacks("connections")
	store.runCallbacks("devices")
}
<commit_msg>Keep track if any devices went offline<commit_after>package store

import "github.com/stampzilla/stampzilla-go/nodes/stampzilla-server/models"

func (store *Store) GetConnections() Connections {
	store.RLock()
	defer store.RUnlock()
	return store.Connections
}

func (store *Store) Connection(id string) *models.Connection {
	store.RLock()
	defer store.RUnlock()
	if conn, ok := store.Connections[id]; ok {
		return conn
	}
	return nil
}

func (store *Store) AddOrUpdateConnection(id string, c *models.Connection) {
	store.Lock()
	store.Connections[id] = c
	store.Unlock()

	store.runCallbacks("connections")
}

func (store *Store) ConnectionChanged() {
	store.runCallbacks("connections")
}

func (store *Store) RemoveConnection(id string) {
	store.Lock()
	delete(store.Connections, id)

	modified := false
	for _, device := range store.Devices.All() {
		if device.ID.Node == id {
			modified = modified || device.Online
			device.Online = false
		}
	}
	store.Unlock()

	store.runCallbacks("connections")
	if modified {
		store.runCallbacks("devices")
	}
}
<|endoftext|>


<commit_before>package instagram

import (
	"strings"
	"time"

	"sync"

	"strconv"

	"net/url"

	"github.com/Seklfreak/Robyul2/cache"
	"github.com/Seklfreak/Robyul2/helpers"
	"github.com/Seklfreak/Robyul2/metrics"
	"github.com/Seklfreak/Robyul2/models"
	goinstaResponse "github.com/ahmdrz/goinsta/response"
	"github.com/globalsign/mgo/bson"
)

var (
	instagramEntryLocks = make(map[string]*sync.Mutex)
)

const (
	InstagramGraphQlWorkers = 15
)

func (m *Handler) checkInstagramGraphQlFeedLoop() {
	log := cache.GetLogger().WithField("module", "instagram")

	defer helpers.Recover()
	defer func() {
		go func() {
			defer helpers.Recover()
			log.Error("The checkInstagramGraphQlFeedLoop died." +
				"Please investigate! Will be restarted in 60 seconds")
			time.Sleep(60 * time.Second)
			m.checkInstagramGraphQlFeedLoop()
		}()
	}()

	for {
		bundledEntries, entriesCount, err := m.getBundledEntries()
		helpers.Relax(err)

		log.Infof(
			"checking graphql feed on %d accounts for %d feeds with %d workers",
			len(bundledEntries), entriesCount, InstagramGraphQlWorkers)
		start := time.Now()

		jobs := make(chan map[string][]models.InstagramEntry, 0)
		results := make(chan int, 0)

		workerEntries := make(map[int]map[string][]models.InstagramEntry, 0)
		for w := 1; w <= InstagramGraphQlWorkers; w++ {
			go m.checkInstagramGraphQlFeedWorker(w, jobs, results)
			workerEntries[w] = make(map[string][]models.InstagramEntry)
		}

		lastWorker := 1
		for code, codeEntries := range bundledEntries {
			workerEntries[lastWorker][code] = codeEntries
			lastWorker++
			if lastWorker > InstagramGraphQlWorkers {
				lastWorker = 1
			}
		}

		for _, workerEntry := range workerEntries {
			jobs <- workerEntry
		}
		close(jobs)

		for a := 1; a <= InstagramGraphQlWorkers; a++ {
			<-results
		}
		elapsed := time.Since(start)
		log.Infof(
			"checked graphql feed on %d accounts for %d feeds with %d workers, took %s",
			len(bundledEntries), entriesCount, InstagramGraphQlWorkers, elapsed)
		metrics.InstagramGraphQlFeedRefreshTime.Set(elapsed.Seconds())

		if entriesCount <= 10 {
			time.Sleep(60 * time.Second)
		}
	}
}

func (m *Handler) checkInstagramGraphQlFeedWorker(id int, jobs <-chan map[string][]models.InstagramEntry, results chan<- int) {
	defer helpers.Recover()

	currentProxy, err := helpers.GetRandomProxy()
	helpers.Relax(err)
	cache.GetLogger().WithField("module", "instagram").Infof("switched to random proxy")

	for job := range jobs {
		//cache.GetLogger().WithField("module", "instagram").WithField("worker", id).Infof(
		//	"worker %d started for %d accounts", id, len(job))
	NextEntry:
		for instagramAccountID, entries := range job {
			//cache.GetLogger().WithField("module", "instagram").WithField("worker", id).Infof(
			//	"checking graphql feed for %d for %d channels", instagramAccountID, len(entries))
		RetryGraphQl:
			receivedPosts, err := m.getPosts(instagramAccountID, currentProxy)
			if err != nil {
				if m.retryOnError(err) {
					cache.GetLogger().WithField("module", "instagram").Infof(
						"proxy error connecting to Instagram Account %s (GraphQL), "+
							"waiting 5 seconds, switching proxy and then trying again", instagramAccountID)
					time.Sleep(5 * time.Second)
					currentProxy, err = helpers.GetRandomProxy()
					helpers.Relax(err)
					goto RetryGraphQl
				}
				helpers.RelaxLog(err)
				continue NextEntry
			}

			for _, receivedPost := range receivedPosts {
				postHasBeenPostedEverywhere := true
				for _, entry := range entries {
					postAlreadyPosted := false
					if receivedPost.CreatedAt.Before(entry.LastPostCheck) {
						postAlreadyPosted = true
					}
					if !postAlreadyPosted {
						postHasBeenPostedEverywhere = false
					}
				}

				if postHasBeenPostedEverywhere {
					continue
				}

				// download specific post data
			RetryPost:
				post, err := m.getPostInformation(receivedPost.Shortcode, currentProxy)
				if err != nil {
					if m.retryOnError(err) {
						cache.GetLogger().WithField("module", "instagram").Infof(
							"hit rate limit checking Instagram Account %s (GraphQL), "+
								"waiting 5 seconds, switching proxy and then trying again", instagramAccountID)
						time.Sleep(5 * time.Second)
						currentProxy, err = helpers.GetRandomProxy()
						helpers.Relax(err)
						goto RetryPost
					}
					helpers.RelaxLog(err)
					continue NextEntry
				}

				for _, entry := range entries {
					entryID := entry.ID
					m.lockEntry(entryID)

					var entry models.InstagramEntry
					err = helpers.MdbOneWithoutLogging(
						helpers.MdbCollection(models.InstagramTable).Find(bson.M{"_id": entryID}),
						&entry,
					)

					if entry.LastPostCheck.IsZero() { // prevent spam
						entry.LastPostCheck = time.Now()
					}

					if err != nil {
						m.unlockEntry(entryID)
						helpers.RelaxLog(err)
						continue
					}

					postAlreadyPosted := false
					if receivedPost.CreatedAt.Before(entry.LastPostCheck) {
						postAlreadyPosted = true
					}

					if postAlreadyPosted == false {
						cache.GetLogger().WithField("module", "instagram").Infof("Posting Post (GraphQL): #%s", post.ID)
						go m.postPostToChannel(entry.ChannelID, post, entry.SendPostType)
					}

					entry.LastPostCheck = time.Now()
					err = helpers.MDbUpdateWithoutLogging(models.InstagramTable, entry.ID, entry)
					if err != nil {
						m.unlockEntry(entryID)
						helpers.RelaxLog(err)
						continue
					}

					m.unlockEntry(entryID)
				}
			}
		}
	}
	results <- len(jobs)
}

func (m *Handler) checkInstagramStoryLoop() {
	log := cache.GetLogger()

	defer helpers.Recover()
	defer func() {
		go func() {
			log.WithField("module", "instagram").Error("The checkInstagramStoryLoop died." +
				"Please investigate! Will be restarted in 60 seconds")
			time.Sleep(60 * time.Second)
			m.checkInstagramStoryLoop()
		}()
	}()

	for {
		bundledEntries, entriesCount, err := m.getBundledEntries()
		helpers.Relax(err)

		cache.GetLogger().WithField("module", "instagram").Infof(
			"checking story on %d accounts for %d feeds", len(bundledEntries), entriesCount)
		start := time.Now()

		for instagramAccountID, entries := range bundledEntries {
		RetryAccount:
			// log.WithField("module", "instagram").Debug(fmt.Sprintf("checking Instagram Account @%s", instagramUsername))

			var posts goinstaResponse.UserFeedResponse
			userIdInt, err := strconv.Atoi(instagramAccountID)
			helpers.Relax(err)
			story, err := instagramClient.GetUserStories(int64(userIdInt))
			if err != nil || story.Status != "ok" {
				if m.retryOnError(err) {
					cache.GetLogger().WithField("module", "instagram").Infof(
						"hit rate limit checking Instagram Account (Stories) %s, "+
							"sleeping for 20 seconds and then trying again", instagramAccountID)
					time.Sleep(20 * time.Second)
					goto RetryAccount
				}
				log.WithField("module", "instagram").Warnf(
					"updating instagram account %s (Story) failed: %s", instagramAccountID, err)
				continue
			}

			// https://github.com/golang/go/wiki/SliceTricks#reversing
			for i := len(posts.Items)/2 - 1; i >= 0; i-- {
				opp := len(posts.Items) - 1 - i
				posts.Items[i], posts.Items[opp] = posts.Items[opp], posts.Items[i]
			}
			for i := len(story.Reel.Items)/2 - 1; i >= 0; i-- {
				opp := len(story.Reel.Items) - 1 - i
				story.Reel.Items[i], story.Reel.Items[opp] = story.Reel.Items[opp], story.Reel.Items[i]
			}

			for _, entry := range entries {
				changes := false

				entryID := entry.ID
				m.lockEntry(entryID)

				var entry models.InstagramEntry
				err = helpers.MdbOne(
					helpers.MdbCollection(models.InstagramTable).Find(bson.M{"_id": entryID}),
					&entry,
				)
				if err != nil {
					m.unlockEntry(entryID)
					if !strings.Contains(err.Error(), "The result does not contain any more rows") {
						helpers.RelaxLog(err)
					}
					continue
				}

				for n, reelMedia := range story.Reel.Items {
					reelMediaAlreadyPosted := false
					for _, reelMediaPostPosted := range entry.PostedPosts {
						if reelMediaPostPosted.Type == models.InstagramPostTypeReel && reelMediaPostPosted.ID == reelMedia.ID {
							reelMediaAlreadyPosted = true
						}
					}
					if reelMediaAlreadyPosted == false {
						log.WithField("module", "instagram").Infof(
							"Posting Reel Media (Story): #%s", reelMedia.ID)
						entry.PostedPosts = append(entry.PostedPosts,
							models.InstagramPostEntry{
								ID:            reelMedia.ID,
								Type:          models.InstagramPostTypeReel,
								CreatedAtTime: time.Unix(int64(reelMedia.DeviceTimestamp), 0),
							})
						changes = true
						go m.postReelMediaToChannel(entry.ChannelID, story, n, entry.SendPostType)
					}
				}

				if changes == true {
					err = helpers.MDbUpdate(models.InstagramTable, entry.ID, entry)
					if err != nil {
						m.unlockEntry(entryID)
						helpers.RelaxLog(err)
						continue
					}
				}

				m.unlockEntry(entryID)
			}
			time.Sleep(1 * time.Second)
		}

		elapsed := time.Since(start)
		cache.GetLogger().WithField("module", "instagram").Infof(
			"checked story on %d accounts for %d feeds, took %s",
			len(bundledEntries), entriesCount, elapsed)
		metrics.InstagramRefreshTime.Set(elapsed.Seconds())

		if entriesCount <= 10 {
			time.Sleep(30 * time.Second)
		}
	}
}

func (m *Handler) lockEntry(entryID bson.ObjectId) {
	if _, ok := instagramEntryLocks[string(entryID)]; ok {
		instagramEntryLocks[string(entryID)].Lock()
		return
	}
	instagramEntryLocks[string(entryID)] = new(sync.Mutex)
	instagramEntryLocks[string(entryID)].Lock()
}

func (m *Handler) unlockEntry(entryID bson.ObjectId) {
	if _, ok := instagramEntryLocks[string(entryID)]; ok {
		instagramEntryLocks[string(entryID)].Unlock()
	}
}

func (m *Handler) retryOnError(err error) (retry bool) {
	if err != nil {
		if _, ok := err.(*url.Error); ok ||
			strings.Contains(err.Error(), "expected status 200; got 429") ||
			strings.Contains(err.Error(), "Please wait a few minutes before you try again.") {
			return true
		}
	}
	return false
}
<commit_msg>[instagram] reduces logging 🚯<commit_after>package instagram

import (
	"strings"
	"time"

	"sync"

	"strconv"

	"net/url"

	"github.com/Seklfreak/Robyul2/cache"
	"github.com/Seklfreak/Robyul2/helpers"
	"github.com/Seklfreak/Robyul2/metrics"
	"github.com/Seklfreak/Robyul2/models"
	goinstaResponse "github.com/ahmdrz/goinsta/response"
	"github.com/globalsign/mgo/bson"
)

var (
	instagramEntryLocks = make(map[string]*sync.Mutex)
)

const (
	InstagramGraphQlWorkers = 15
)

func (m *Handler) checkInstagramGraphQlFeedLoop() {
	log := cache.GetLogger().WithField("module", "instagram")

	defer helpers.Recover()
	defer func() {
		go func() {
			defer helpers.Recover()
			log.Error("The checkInstagramGraphQlFeedLoop died." +
				"Please investigate! Will be restarted in 60 seconds")
			time.Sleep(60 * time.Second)
			m.checkInstagramGraphQlFeedLoop()
		}()
	}()

	for {
		bundledEntries, entriesCount, err := m.getBundledEntries()
		helpers.Relax(err)

		log.Infof(
			"checking graphql feed on %d accounts for %d feeds with %d workers",
			len(bundledEntries), entriesCount, InstagramGraphQlWorkers)
		start := time.Now()

		jobs := make(chan map[string][]models.InstagramEntry, 0)
		results := make(chan int, 0)

		workerEntries := make(map[int]map[string][]models.InstagramEntry, 0)
		for w := 1; w <= InstagramGraphQlWorkers; w++ {
			go m.checkInstagramGraphQlFeedWorker(w, jobs, results)
			workerEntries[w] = make(map[string][]models.InstagramEntry)
		}

		lastWorker := 1
		for code, codeEntries := range bundledEntries {
			workerEntries[lastWorker][code] = codeEntries
			lastWorker++
			if lastWorker > InstagramGraphQlWorkers {
				lastWorker = 1
			}
		}

		for _, workerEntry := range workerEntries {
			jobs <- workerEntry
		}
		close(jobs)

		for a := 1; a <= InstagramGraphQlWorkers; a++ {
			<-results
		}
		elapsed := time.Since(start)
		log.Infof(
			"checked graphql feed on %d accounts for %d feeds with %d workers, took %s",
			len(bundledEntries), entriesCount, InstagramGraphQlWorkers, elapsed)
		metrics.InstagramGraphQlFeedRefreshTime.Set(elapsed.Seconds())

		if entriesCount <= 10 {
			time.Sleep(60 * time.Second)
		}
	}
}

func (m *Handler) checkInstagramGraphQlFeedWorker(id int, jobs <-chan map[string][]models.InstagramEntry, results chan<- int) {
	defer helpers.Recover()

	currentProxy, err := helpers.GetRandomProxy()
	helpers.Relax(err)
	cache.GetLogger().WithField("module", "instagram").Infof("switched to random proxy")

	for job := range jobs {
		//cache.GetLogger().WithField("module", "instagram").WithField("worker", id).Infof(
		//	"worker %d started for %d accounts", id, len(job))
	NextEntry:
		for instagramAccountID, entries := range job {
			//cache.GetLogger().WithField("module", "instagram").WithField("worker", id).Infof(
			//	"checking graphql feed for %d for %d channels", instagramAccountID, len(entries))
		RetryGraphQl:
			receivedPosts, err := m.getPosts(instagramAccountID, currentProxy)
			if err != nil {
				if m.retryOnError(err) {
					//cache.GetLogger().WithField("module", "instagram").Infof(
					//	"proxy error connecting to Instagram Account %s (GraphQL), "+
					//		"waiting 5 seconds, switching proxy and then trying again", instagramAccountID)
					time.Sleep(5 * time.Second)
					currentProxy, err = helpers.GetRandomProxy()
					helpers.Relax(err)
					goto RetryGraphQl
				}
				helpers.RelaxLog(err)
				continue NextEntry
			}

			for _, receivedPost := range receivedPosts {
				postHasBeenPostedEverywhere := true
				for _, entry := range entries {
					postAlreadyPosted := false
					if receivedPost.CreatedAt.Before(entry.LastPostCheck) {
						postAlreadyPosted = true
					}
					if !postAlreadyPosted {
						postHasBeenPostedEverywhere = false
					}
				}

				if postHasBeenPostedEverywhere {
					continue
				}

				// download specific post data
			RetryPost:
				post, err := m.getPostInformation(receivedPost.Shortcode, currentProxy)
				if err != nil {
					if m.retryOnError(err) {
						//cache.GetLogger().WithField("module", "instagram").Infof(
						//	"hit rate limit checking Instagram Account %s (GraphQL), "+
						//		"waiting 5 seconds, switching proxy and then trying again", instagramAccountID)
						time.Sleep(5 * time.Second)
						currentProxy, err = helpers.GetRandomProxy()
						helpers.Relax(err)
						goto RetryPost
					}
					helpers.RelaxLog(err)
					continue NextEntry
				}

				for _, entry := range entries {
					entryID := entry.ID
					m.lockEntry(entryID)

					var entry models.InstagramEntry
					err = helpers.MdbOneWithoutLogging(
						helpers.MdbCollection(models.InstagramTable).Find(bson.M{"_id": entryID}),
						&entry,
					)

					if entry.LastPostCheck.IsZero() { // prevent spam
						entry.LastPostCheck = time.Now()
					}

					if err != nil {
						m.unlockEntry(entryID)
						helpers.RelaxLog(err)
						continue
					}

					postAlreadyPosted := false
					if receivedPost.CreatedAt.Before(entry.LastPostCheck) {
						postAlreadyPosted = true
					}

					if postAlreadyPosted == false {
						cache.GetLogger().WithField("module", "instagram").Infof("Posting Post (GraphQL): #%s", post.ID)
						go m.postPostToChannel(entry.ChannelID, post, entry.SendPostType)
					}

					entry.LastPostCheck = time.Now()
					err = helpers.MDbUpdateWithoutLogging(models.InstagramTable, entry.ID, entry)
					if err != nil {
						m.unlockEntry(entryID)
						helpers.RelaxLog(err)
						continue
					}

					m.unlockEntry(entryID)
				}
			}
		}
	}
	results <- len(jobs)
}

func (m *Handler) checkInstagramStoryLoop() {
	log := cache.GetLogger()

	defer helpers.Recover()
	defer func() {
		go func() {
			log.WithField("module", "instagram").Error("The checkInstagramStoryLoop died." +
				"Please investigate! Will be restarted in 60 seconds")
			time.Sleep(60 * time.Second)
			m.checkInstagramStoryLoop()
		}()
	}()

	for {
		bundledEntries, entriesCount, err := m.getBundledEntries()
		helpers.Relax(err)

		cache.GetLogger().WithField("module", "instagram").Infof(
			"checking story on %d accounts for %d feeds", len(bundledEntries), entriesCount)
		start := time.Now()

		for instagramAccountID, entries := range bundledEntries {
		RetryAccount:
			// log.WithField("module", "instagram").Debug(fmt.Sprintf("checking Instagram Account @%s", instagramUsername))

			var posts goinstaResponse.UserFeedResponse
			userIdInt, err := strconv.Atoi(instagramAccountID)
			helpers.Relax(err)
			story, err := instagramClient.GetUserStories(int64(userIdInt))
			if err != nil || story.Status != "ok" {
				if m.retryOnError(err) {
					cache.GetLogger().WithField("module", "instagram").Infof(
						"hit rate limit checking Instagram Account (Stories) %s, "+
							"sleeping for 20 seconds and then trying again", instagramAccountID)
					time.Sleep(20 * time.Second)
					goto RetryAccount
				}
				log.WithField("module", "instagram").Warnf(
					"updating instagram account %s (Story) failed: %s", instagramAccountID, err)
				continue
			}

			// https://github.com/golang/go/wiki/SliceTricks#reversing
			for i := len(posts.Items)/2 - 1; i >= 0; i-- {
				opp := len(posts.Items) - 1 - i
				posts.Items[i], posts.Items[opp] = posts.Items[opp], posts.Items[i]
			}
			for i := len(story.Reel.Items)/2 - 1; i >= 0; i-- {
				opp := len(story.Reel.Items) - 1 - i
				story.Reel.Items[i], story.Reel.Items[opp] = story.Reel.Items[opp], story.Reel.Items[i]
			}

			for _, entry := range entries {
				changes := false

				entryID := entry.ID
				m.lockEntry(entryID)

				var entry models.InstagramEntry
				err = helpers.MdbOne(
					helpers.MdbCollection(models.InstagramTable).Find(bson.M{"_id": entryID}),
					&entry,
				)
				if err != nil {
					m.unlockEntry(entryID)
					if !strings.Contains(err.Error(), "The result does not contain any more rows") {
						helpers.RelaxLog(err)
					}
					continue
				}

				for n, reelMedia := range story.Reel.Items {
					reelMediaAlreadyPosted := false
					for _, reelMediaPostPosted := range entry.PostedPosts {
						if reelMediaPostPosted.Type == models.InstagramPostTypeReel && reelMediaPostPosted.ID == reelMedia.ID {
							reelMediaAlreadyPosted = true
						}
					}
					if reelMediaAlreadyPosted == false {
						log.WithField("module", "instagram").Infof(
							"Posting Reel Media (Story): #%s", reelMedia.ID)
						entry.PostedPosts = append(entry.PostedPosts,
							models.InstagramPostEntry{
								ID:            reelMedia.ID,
								Type:          models.InstagramPostTypeReel,
								CreatedAtTime: time.Unix(int64(reelMedia.DeviceTimestamp), 0),
							})
						changes = true
						go m.postReelMediaToChannel(entry.ChannelID, story, n, entry.SendPostType)
					}
				}

				if changes == true {
					err = helpers.MDbUpdate(models.InstagramTable, entry.ID, entry)
					if err != nil {
						m.unlockEntry(entryID)
						helpers.RelaxLog(err)
						continue
					}
				}

				m.unlockEntry(entryID)
			}
			time.Sleep(1 * time.Second)
		}

		elapsed := time.Since(start)
		cache.GetLogger().WithField("module", "instagram").Infof(
			"checked story on %d accounts for %d feeds, took %s",
			len(bundledEntries), entriesCount, elapsed)
		metrics.InstagramRefreshTime.Set(elapsed.Seconds())

		if entriesCount <= 10 {
			time.Sleep(30 * time.Second)
		}
	}
}

func (m *Handler) lockEntry(entryID bson.ObjectId) {
	if _, ok := instagramEntryLocks[string(entryID)]; ok {
		instagramEntryLocks[string(entryID)].Lock()
		return
	}
	instagramEntryLocks[string(entryID)] = new(sync.Mutex)
	instagramEntryLocks[string(entryID)].Lock()
}

func (m *Handler) unlockEntry(entryID bson.ObjectId) {
	if _, ok := instagramEntryLocks[string(entryID)]; ok {
		instagramEntryLocks[string(entryID)].Unlock()
	}
}

func (m *Handler) retryOnError(err error) (retry bool) {
	if err != nil {
		if _, ok := err.(*url.Error); ok ||
			strings.Contains(err.Error(), "expected status 200; got 429") ||
			strings.Contains(err.Error(), "Please wait a few minutes before you try again.") {
			return true
		}
	}
	return false
}
<|endoftext|>


<commit_before>package main

import (
	"log"
	"os"
	"path"
	"strings"

	"github.com/num5/chca/conf"
	"github.com/num5/chca/template"
	"github.com/num5/chca/utils"
	"time"
)

var data = map[string]interface{}{
	"title":       conf.SiteTitle(),
	"subtitle":    conf.SiteSubTitle(),
	"description": conf.SiteDescription(),
	"keywords":    conf.SiteKeywords(),
	"author":      conf.Author(),
	"avatar":      conf.Avatar(),
	"github":      conf.Github(),
	"weibo":       conf.Weibo(),
}

func Compile() {
	checkFile()
	copy()

	LoadArticle()
	// 创建页面
	CompileHome()
	CompileArticle()
	CompileArchive()
	CompileTagPage()
	CompileCatePage()
	CompileCategory()
	CompileTag()
	CompileAbout()
}

// 编译主页
func CompileHome() {

	data["artlist"] = GetAllArt()
	data["cate"] = GetCate()
	data["tpl"] = conf.DirTheme() + "/layout/index.html"

	err := utils.MkDir(conf.DirHtml())

	if err != nil {
		panic(err)
	}

	homepath := path.Join(conf.DirHtml(), "index.html")

	htmlfile, err := os.Create(homepath)
	if err != nil {
		panic(err)
	}

	t, _ := template.New(conf.DirTheme() + "/layout/main.html")
	t = t.Funcs(template.FuncMap{"unescaped": utils.Unescaped, "cmonth": utils.CMonth, "format": utils.Format, "count": utils.Count, "lt": utils.Lt, "gt": utils.Gt, "eq": utils.Eq})
	t.Walk(conf.DirTheme()+`/layout`, ".html")
	t.Execute(htmlfile, data)
}

// 编译文章页
func CompileArticle() {
	artlist := GetAllArt()

	for _, art := range artlist {
		data["tpl"] = conf.DirTheme() + "/layout/post.html"

		data["description"] = art.Summary
		data["keywords"] = strings.Join(art.Tags, ",")

		data["article"] = art
		data["cate"] = GetCate()

		url := CreatePostLink(art)
		filepath := path.Join(conf.DirHtml(), url)

		err := utils.MkDir(filepath)

		if err != nil {
			panic(err)
		}

		filename := path.Join(filepath, "index.html")

		htmlfile, err := os.Create(filename)

		if err != nil {
			panic(err)
		}

		t, _ := template.New(conf.DirTheme() + "/layout/main.html")
		t = t.Funcs(template.FuncMap{"unescaped": utils.Unescaped, "cmonth": utils.CMonth, "format": utils.Format, "count": utils.Count, "lt": utils.Lt, "gt": utils.Gt, "eq": utils.Eq})
		t.Walk(conf.DirTheme()+`/layout`, ".html")
		t.Execute(htmlfile, data)
	}
}

// 编译about页
func CompileAbout() {
	about, err := GetAbout()
	if err != nil {
		panic(err)
	}

	data["tpl"] = conf.DirTheme() + "/layout/post.html"

	data["article"] = about
	data["cate"] = GetCate()

	filepath := path.Join(conf.DirHtml(), "about.html")

	htmlfile, err := os.Create(filepath)

	if err != nil {
		panic(err)
	}

	t, _ := template.New(conf.DirTheme() + "/layout/main.html")
	t = t.Funcs(template.FuncMap{"unescaped": utils.Unescaped, "cmonth": utils.CMonth, "format": utils.Format, "count": utils.Count, "lt": utils.Lt, "gt": utils.Gt, "eq": utils.Eq})
	t.Walk(conf.DirTheme()+`/layout`, ".html")
	t.Execute(htmlfile, data)
}

// 编译归档页
func CompileArchive() {

	data["archive"] = GetArchive()
	data["cate"] = GetCate()
	data["tpl"] = conf.DirTheme() + "/layout/archive.html"

	filepath := path.Join(conf.DirHtml(), "archive")

	err := utils.MkDir(filepath)

	if err != nil {
		panic(err)
	}

	filename := path.Join(filepath, "index.html")

	htmlfile, err := os.Create(filename)

	if err != nil {
		panic(err)
	}

	t, _ := template.New(conf.DirTheme() + "/layout/main.html")
	t = t.Funcs(template.FuncMap{"unescaped": utils.Unescaped, "cmonth": utils.CMonth, "format": utils.Format, "count": utils.Count, "lt": utils.Lt, "gt": utils.Gt, "eq": utils.Eq})
	t.Walk(conf.DirTheme()+`/layout`, ".html")
	t.Execute(htmlfile, data)
}

// 编译cate导航页
func CompileCatePage() {

	data["cate"] = GetCate()
	data["tpl"] = conf.DirTheme() + "/layout/category.html"

	filepath := path.Join(conf.DirHtml(), "category")

	err := utils.MkDir(filepath)

	if err != nil {
		panic(err)
	}

	filename := path.Join(filepath, "index.html")

	htmlfile, err := os.Create(filename)

	if err != nil {
		panic(err)
	}

	t, _ := template.New(conf.DirTheme() + "/layout/main.html")
	t = t.Funcs(template.FuncMap{"unescaped": utils.Unescaped, "cmonth": utils.CMonth, "format": utils.Format, "count": utils.Count, "lt": utils.Lt, "gt": utils.Gt, "eq": utils.Eq})
	t.Walk(conf.DirTheme()+`/layout`, ".html")
	t.Execute(htmlfile, data)
}

// 编译category页面
func CompileCategory() {

	cates := GetCate()
	data["cate"] = cates

	for _, cate := range cates {

		data["ptitle"] = cate.Name
		data["content"] = cate.Posts
		data["count"] = cate.Count
		data["tpl"] = conf.DirTheme() + "/layout/page.html"

		filepath := path.Join(conf.DirHtml(), "category", cate.Name)

		err := utils.MkDir(filepath)

		if err != nil {
			panic(err)
		}

		filename := path.Join(filepath, "index.html")

		htmlfile, err := os.Create(filename)

		if err != nil {
			panic(err)
		}

		t, _ := template.New(conf.DirTheme() + "/layout/main.html")
		t = t.Funcs(template.FuncMap{"unescaped": utils.Unescaped, "cmonth": utils.CMonth, "format": utils.Format, "count": utils.Count, "lt": utils.Lt, "gt": utils.Gt, "eq": utils.Eq})
		t.Walk(conf.DirTheme()+`/layout`, ".html")
		t.Execute(htmlfile, data)
	}

}

// 编译tag导航页
func CompileTagPage() {

	data["cate"] = GetCate()
	data["tag"] = GetTag()
	data["tpl"] = conf.DirTheme() + "/layout/tag.html"

	filepath := path.Join(conf.DirHtml(), "tag")

	err := utils.MkDir(filepath)

	if err != nil {
		panic(err)
	}

	filename := path.Join(filepath, "index.html")

	htmlfile, err := os.Create(filename)

	if err != nil {
		panic(err)
	}

	t, _ := template.New(conf.DirTheme() + "/layout/main.html")
	t = t.Funcs(template.FuncMap{"unescaped": utils.Unescaped, "cmonth": utils.CMonth, "format": utils.Format, "count": utils.Count, "lt": utils.Lt, "gt": utils.Gt, "eq": utils.Eq})
	t.Walk(conf.DirTheme()+`/layout`, ".html")
	t.Execute(htmlfile, data)
}

// 编译tag页面
func CompileTag() {

	tags := GetTag()
	data["cate"] = GetCate()

	for _, tag := range tags {

		data["ptitle"] = tag.Name
		data["content"] = tag.Posts
		data["count"] = tag.Count
		data["tpl"] = conf.DirTheme() + "/layout/page.html"

		filepath := path.Join(conf.DirHtml(), "tag", tag.Name)

		err := utils.MkDir(filepath)

		if err != nil {
			panic(err)
		}

		filename := path.Join(filepath, "index.html")

		htmlfile, err := os.Create(filename)

		if err != nil {
			panic(err)
		}

		t, _ := template.New(conf.DirTheme() + "/layout/main.html")
		t = t.Funcs(template.FuncMap{"unescaped": utils.Unescaped, "cmonth": utils.CMonth, "format": utils.Format, "count": utils.Count, "lt": utils.Lt, "gt": utils.Gt, "eq": utils.Eq})
		t.Walk(conf.DirTheme()+`/layout`, ".html")
		t.Execute(htmlfile, data)
	}

}

func CrearteMark(filename string) string {
	file := path.Join(conf.DirMark(), filename+".md")

	_, err := os.Stat(file)
	if !os.IsNotExist(err) {
		log.Println("已存在文件")
		os.Exit(1)
	}

	src, err := utils.CreateFile(conf.DirMark(), filename+".md")
	if err != nil {
		panic(err)
	}

	date := time.Now().Format("2006-01-02")
	masthead := `---
date: `+ date +`
title:
categories:
- 技术
tags:
-
-
---`
	err = utils.WriteFile(src, masthead)
	if err != nil {
		panic(err)
	}

	return src
}

func copy() {

	// copy 配置文件
	/*_, err := utils.CopyFile("conf.ini", path.Join(conf.DirHtml(), "conf.ini"))
	  if err != nil {
	      panic(err)
	  }*/

	err := utils.CopyDir(path.Join(conf.DirTheme(), "assets"), path.Join(conf.DirHtml(), "assets"))
	if err != nil {
		panic(err)
	}

}

// 使用 git 更新 markdown 文件
func remote_md() {

}

func checkFile() {
	if _, err := os.Stat(conf.DirTheme()); os.IsNotExist(err) {
		log.Println("需要先初始化并添加模板文件")
		os.Exit(2)
	}

	/*if _, err := os.Stat(conf.DirStor()); os.IsNotExist(err) {
		panic("需要先初始化")
	}*/
}
<commit_msg>create file add masthead<commit_after>package main

import (
	"log"
	"os"
	"path"
	"strings"

	"github.com/num5/chca/conf"
	"github.com/num5/chca/template"
	"github.com/num5/chca/utils"
	"time"
)

var data = map[string]interface{}{
	"title":       conf.SiteTitle(),
	"subtitle":    conf.SiteSubTitle(),
	"description": conf.SiteDescription(),
	"keywords":    conf.SiteKeywords(),
	"author":      conf.Author(),
	"avatar":      conf.Avatar(),
	"github":      conf.Github(),
	"weibo":       conf.Weibo(),
}

func Compile() {
	checkFile()
	copy()

	LoadArticle()
	// 创建页面
	CompileHome()
	CompileArticle()
	CompileArchive()
	CompileTagPage()
	CompileCatePage()
	CompileCategory()
	CompileTag()
	CompileAbout()
}

// 编译主页
func CompileHome() {

	data["artlist"] = GetAllArt()
	data["cate"] = GetCate()
	data["tpl"] = conf.DirTheme() + "/layout/index.html"

	err := utils.MkDir(conf.DirHtml())

	if err != nil {
		panic(err)
	}

	homepath := path.Join(conf.DirHtml(), "index.html")

	htmlfile, err := os.Create(homepath)
	if err != nil {
		panic(err)
	}

	t, _ := template.New(conf.DirTheme() + "/layout/main.html")
	t = t.Funcs(template.FuncMap{"unescaped": utils.Unescaped, "cmonth": utils.CMonth, "format": utils.Format, "count": utils.Count, "lt": utils.Lt, "gt": utils.Gt, "eq": utils.Eq})
	t.Walk(conf.DirTheme()+`/layout`, ".html")
	t.Execute(htmlfile, data)
}

// 编译文章页
func CompileArticle() {
	artlist := GetAllArt()

	for _, art := range artlist {
		data["tpl"] = conf.DirTheme() + "/layout/post.html"

		data["description"] = art.Summary
		data["keywords"] = strings.Join(art.Tags, ",")

		data["article"] = art
		data["cate"] = GetCate()

		url := CreatePostLink(art)
		filepath := path.Join(conf.DirHtml(), url)

		err := utils.MkDir(filepath)

		if err != nil {
			panic(err)
		}

		filename := path.Join(filepath, "index.html")

		htmlfile, err := os.Create(filename)

		if err != nil {
			panic(err)
		}

		t, _ := template.New(conf.DirTheme() + "/layout/main.html")
		t = t.Funcs(template.FuncMap{"unescaped": utils.Unescaped, "cmonth": utils.CMonth, "format": utils.Format, "count": utils.Count, "lt": utils.Lt, "gt": utils.Gt, "eq": utils.Eq})
		t.Walk(conf.DirTheme()+`/layout`, ".html")
		t.Execute(htmlfile, data)
	}
}

// 编译about页
func CompileAbout() {
	about, err := GetAbout()
	if err != nil {
		panic(err)
	}

	data["tpl"] = conf.DirTheme() + "/layout/post.html"

	data["article"] = about
	data["cate"] = GetCate()

	filepath := path.Join(conf.DirHtml(), "about.html")

	htmlfile, err := os.Create(filepath)

	if err != nil {
		panic(err)
	}

	t, _ := template.New(conf.DirTheme() + "/layout/main.html")
	t = t.Funcs(template.FuncMap{"unescaped": utils.Unescaped, "cmonth": utils.CMonth, "format": utils.Format, "count": utils.Count, "lt": utils.Lt, "gt": utils.Gt, "eq": utils.Eq})
	t.Walk(conf.DirTheme()+`/layout`, ".html")
	t.Execute(htmlfile, data)
}

// 编译归档页
func CompileArchive() {

	data["archive"] = GetArchive()
	data["cate"] = GetCate()
	data["tpl"] = conf.DirTheme() + "/layout/archive.html"

	filepath := path.Join(conf.DirHtml(), "archive")

	err := utils.MkDir(filepath)

	if err != nil {
		panic(err)
	}

	filename := path.Join(filepath, "index.html")

	htmlfile, err := os.Create(filename)

	if err != nil {
		panic(err)
	}

	t, _ := template.New(conf.DirTheme() + "/layout/main.html")
	t = t.Funcs(template.FuncMap{"unescaped": utils.Unescaped, "cmonth": utils.CMonth, "format": utils.Format, "count": utils.Count, "lt": utils.Lt, "gt": utils.Gt, "eq": utils.Eq})
	t.Walk(conf.DirTheme()+`/layout`, ".html")
	t.Execute(htmlfile, data)
}

// 编译cate导航页
func CompileCatePage() {

	data["cate"] = GetCate()
	data["tpl"] = conf.DirTheme() + "/layout/category.html"

	filepath := path.Join(conf.DirHtml(), "category")

	err := utils.MkDir(filepath)

	if err != nil {
		panic(err)
	}

	filename := path.Join(filepath, "index.html")

	htmlfile, err := os.Create(filename)

	if err != nil {
		panic(err)
	}

	t, _ := template.New(conf.DirTheme() + "/layout/main.html")
	t = t.Funcs(template.FuncMap{"unescaped": utils.Unescaped, "cmonth": utils.CMonth, "format": utils.Format, "count": utils.Count, "lt": utils.Lt, "gt": utils.Gt, "eq": utils.Eq})
	t.Walk(conf.DirTheme()+`/layout`, ".html")
	t.Execute(htmlfile, data)
}

// 编译category页面
func CompileCategory() {

	cates := GetCate()
	data["cate"] = cates

	for _, cate := range cates {

		data["ptitle"] = cate.Name
		data["content"] = cate.Posts
		data["count"] = cate.Count
		data["tpl"] = conf.DirTheme() + "/layout/page.html"

		filepath := path.Join(conf.DirHtml(), "category", cate.Name)

		err := utils.MkDir(filepath)

		if err != nil {
			panic(err)
		}

		filename := path.Join(filepath, "index.html")

		htmlfile, err := os.Create(filename)

		if err != nil {
			panic(err)
		}

		t, _ := template.New(conf.DirTheme() + "/layout/main.html")
		t = t.Funcs(template.FuncMap{"unescaped": utils.Unescaped, "cmonth": utils.CMonth, "format": utils.Format, "count": utils.Count, "lt": utils.Lt, "gt": utils.Gt, "eq": utils.Eq})
		t.Walk(conf.DirTheme()+`/layout`, ".html")
		t.Execute(htmlfile, data)
	}

}

// 编译tag导航页
func CompileTagPage() {

	data["cate"] = GetCate()
	data["tag"] = GetTag()
	data["tpl"] = conf.DirTheme() + "/layout/tag.html"

	filepath := path.Join(conf.DirHtml(), "tag")

	err := utils.MkDir(filepath)

	if err != nil {
		panic(err)
	}

	filename := path.Join(filepath, "index.html")

	htmlfile, err := os.Create(filename)

	if err != nil {
		panic(err)
	}

	t, _ := template.New(conf.DirTheme() + "/layout/main.html")
	t = t.Funcs(template.FuncMap{"unescaped": utils.Unescaped, "cmonth": utils.CMonth, "format": utils.Format, "count": utils.Count, "lt": utils.Lt, "gt": utils.Gt, "eq": utils.Eq})
	t.Walk(conf.DirTheme()+`/layout`, ".html")
	t.Execute(htmlfile, data)
}

// 编译tag页面
func CompileTag() {

	tags := GetTag()
	data["cate"] = GetCate()

	for _, tag := range tags {

		data["ptitle"] = tag.Name
		data["content"] = tag.Posts
		data["count"] = tag.Count
		data["tpl"] = conf.DirTheme() + "/layout/page.html"

		filepath := path.Join(conf.DirHtml(), "tag", tag.Name)

		err := utils.MkDir(filepath)

		if err != nil {
			panic(err)
		}

		filename := path.Join(filepath, "index.html")

		htmlfile, err := os.Create(filename)

		if err != nil {
			panic(err)
		}

		t, _ := template.New(conf.DirTheme() + "/layout/main.html")
		t = t.Funcs(template.FuncMap{"unescaped": utils.Unescaped, "cmonth": utils.CMonth, "format": utils.Format, "count": utils.Count, "lt": utils.Lt, "gt": utils.Gt, "eq": utils.Eq})
		t.Walk(conf.DirTheme()+`/layout`, ".html")
		t.Execute(htmlfile, data)
	}

}

func CrearteMark(filename string) string {
	file := path.Join(conf.DirMark(), filename+".md")

	_, err := os.Stat(file)
	if !os.IsNotExist(err) {
		log.Println("已存在文件")
		os.Exit(1)
	}

	src, err := utils.CreateFile(conf.DirMark(), filename+".md")
	if err != nil {
		panic(err)
	}

	date := time.Now().Format("2006-01-02")
	now := time.Now().Format("15:04:05")
	masthead := `---
date: `+ date +`
time: `+ now +`
title:
categories:
- 技术
tags:
-
-
---`
	err = utils.WriteFile(src, masthead)
	if err != nil {
		panic(err)
	}

	return src
}

func copy() {

	// copy 配置文件
	/*_, err := utils.CopyFile("conf.ini", path.Join(conf.DirHtml(), "conf.ini"))
	  if err != nil {
	      panic(err)
	  }*/

	err := utils.CopyDir(path.Join(conf.DirTheme(), "assets"), path.Join(conf.DirHtml(), "assets"))
	if err != nil {
		panic(err)
	}

}

// 使用 git 更新 markdown 文件
func remote_md() {

}

func checkFile() {
	if _, err := os.Stat(conf.DirTheme()); os.IsNotExist(err) {
		log.Println("需要先初始化并添加模板文件")
		os.Exit(2)
	}

	/*if _, err := os.Stat(conf.DirStor()); os.IsNotExist(err) {
		panic("需要先初始化")
	}*/
}
<|endoftext|>


<commit_before>//  Copyright (c) 2013 Couchbase, Inc.
//  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 rest

import (
	"net/http"
	"time"

	"github.com/couchbase/sync_gateway/auth"
	"github.com/couchbase/sync_gateway/base"
	"github.com/couchbase/sync_gateway/channels"
	"github.com/couchbase/sync_gateway/db"
)

const kDefaultSessionTTL = 24 * time.Hour

// Respond with a JSON struct containing info about the current login session
func (h *handler) respondWithSessionInfo() error {

	response := h.formatSessionResponse(h.user)

	h.writeJSON(response)
	return nil
}

// GET /_session returns info about the current user
func (h *handler) handleSessionGET() error {
	return h.respondWithSessionInfo()
}

// POST /_session creates a login session and sets its cookie
func (h *handler) handleSessionPOST() error {
	// CORS not allowed for login #115 #762
	originHeader := h.rq.Header["Origin"]
	if len(originHeader) > 0 {
		matched := ""
		if h.server.config.CORS != nil {
			matched = matchedOrigin(h.server.config.CORS.LoginOrigin, originHeader)
		}
		if matched == "" {
			return base.HTTPErrorf(http.StatusBadRequest, "No CORS")
		}
	}

	user, err := h.getUserFromSessionRequestBody()

	// If we fail to get a user from the body and we've got a non-GUEST authenticated user, create the session based on that user
	if user == nil && h.user != nil && h.user.Name() != "" {
		return h.makeSession(h.user)
	} else {
		if err != nil {
			return err
		}
		return h.makeSession(user)
	}

}

func (h *handler) getUserFromSessionRequestBody() (auth.User, error) {

	var params struct {
		Name     string `json:"name"`
		Password string `json:"password"`
	}
	err := h.readJSONInto(&params)
	if err != nil {
		return nil, err
	}

	var user auth.User
	user, err = h.db.Authenticator().GetUser(params.Name)
	if err != nil {
		return nil, err
	}

	if user != nil && !user.Authenticate(params.Password) {
		user = nil
	}
	return user, err
}

// DELETE /_session logs out the current session
func (h *handler) handleSessionDELETE() error {
	if len(h.rq.Header["Origin"]) > 0 {
		// CORS not allowed for login #115
		return base.HTTPErrorf(http.StatusBadRequest, "No CORS")
	}
	cookie := h.db.Authenticator().DeleteSessionForCookie(h.rq)
	if cookie == nil {
		return base.HTTPErrorf(http.StatusNotFound, "no session")
	}
	http.SetCookie(h.response, cookie)
	return nil
}

func (h *handler) makeSession(user auth.User) error {

	_, err := h.makeSessionWithTTL(user, kDefaultSessionTTL)
	if err != nil {
		return err
	}
	return h.respondWithSessionInfo()
}

// Creates a session with TTL and adds to the response.  Does NOT return the session info response.
func (h *handler) makeSessionWithTTL(user auth.User, expiry time.Duration) (sessionID string, err error) {
	if user == nil {
		return "", base.HTTPErrorf(http.StatusUnauthorized, "Invalid login")
	}
	h.user = user
	auth := h.db.Authenticator()
	session, err := auth.CreateSession(user.Name(), expiry)
	if err != nil {
		return "", err
	}
	cookie := auth.MakeSessionCookie(session)
	base.AddDbPathToCookie(h.rq, cookie)
	http.SetCookie(h.response, cookie)
	return session.ID, nil
}

func (h *handler) makeSessionFromEmail(email string, createUserIfNeeded bool) error {

	// Email is verified. Look up the user and make a login session for her:
	user, err := h.db.Authenticator().GetUserByEmail(email)
	if err != nil {
		return err
	}
	if user == nil {
		// The email address is authentic but we have no user account for it.
		if !createUserIfNeeded {
			return base.HTTPErrorf(http.StatusUnauthorized, "No such user")
		}

		if len(email) < 1 {
			return base.HTTPErrorf(http.StatusBadRequest, "Cannot register new user: email is missing")
		}

		// Create a User with the given email address as username and a random password.
		user, err = h.db.Authenticator().RegisterNewUser(email, email)
		if err != nil {
			return err
		}
	}
	return h.makeSession(user)

}

// MakeSessionFromUserAndEmail first attempts to find the user by username.  If found, updates the users's
// email if different. If no match for username, attempts to find by the user by email.
// If not found, and createUserIfNeeded=true, creates a new user based on username, email.
func (h *handler) makeSessionFromNameAndEmail(username, email string, createUserIfNeeded bool) error {

	// Username and email are verified. Look up the user and make a login session for her - first
	// attempt lookup by name
	user, err := h.db.Authenticator().GetUser(username)
	if err != nil {
		return err
	}

	// If user found, check whether the email needs to be updated (e.g. user has changed email in
	// external auth system)
	if user != nil {
		if email != user.Email() {
			if err := user.SetEmail(email); err == nil {
				h.db.Authenticator().Save(user)
			}
		}
	} else {
		// User not found by name.  Attempt user lookup by email.  This provides backward
		// compatibility for users that were originally created with id = email
		user, err = h.db.Authenticator().GetUserByEmail(email)
		if err != nil {
			return err
		}
	}
	if user == nil {
		// The user/email are validated, but we don't have a user for either
		if !createUserIfNeeded {
			return base.HTTPErrorf(http.StatusUnauthorized, "No such user")
		}

		if len(email) < 1 {
			return base.HTTPErrorf(http.StatusBadRequest, "Cannot register new user: email is missing")
		}

		// Create a User with the given email address as username and a random password.
		user, err = h.db.Authenticator().RegisterNewUser(username, email)
		if err != nil {
			return err
		}
	}
	return h.makeSession(user)
}

// ADMIN API: Generates a login session for a user and returns the session ID and cookie name.
func (h *handler) createUserSession() error {
	h.assertAdminOnly()
	var params struct {
		Name string `json:"name"`
		TTL  int    `json:"ttl"`
	}
	params.TTL = int(kDefaultSessionTTL / time.Second)
	err := h.readJSONInto(&params)
	if err != nil {
		return err
	} else if params.Name == "" || params.Name == base.GuestUsername || !auth.IsValidPrincipalName(params.Name) {
		return base.HTTPErrorf(http.StatusBadRequest, "Invalid or missing user name")
	} else if user, err := h.db.Authenticator().GetUser(params.Name); user == nil {
		if err == nil {
			err = base.HTTPErrorf(http.StatusNotFound, "No such user %q", params.Name)
		}
		return err
	}

	ttl := time.Duration(params.TTL) * time.Second
	if ttl < 1.0 {
		return base.HTTPErrorf(http.StatusBadRequest, "Invalid or missing ttl")
	}

	session, err := h.db.Authenticator().CreateSession(params.Name, ttl)
	if err != nil {
		return err
	}
	var response struct {
		SessionID  string    `json:"session_id"`
		Expires    time.Time `json:"expires"`
		CookieName string    `json:"cookie_name"`
	}
	response.SessionID = session.ID
	response.Expires = session.Expiration
	response.CookieName = auth.CookieName
	h.writeJSON(response)
	return nil
}

func (h *handler) getUserSession() error {

	h.assertAdminOnly()
	session, err := h.db.Authenticator().GetSession(h.PathVar("sessionid"))

	if session == nil {
		if err == nil {
			err = kNotFoundError
		}
		return err
	}

	return h.respondWithSessionInfoForSession(session)
}

// ADMIN API: Deletes a specified session.  If username is present on the request, validates
// that the session being deleted is associated with the user.
func (h *handler) deleteUserSession() error {
	h.assertAdminOnly()
	userName := h.PathVar("name")
	if userName != "" {
		return h.deleteUserSessionWithValidation(h.PathVar("sessionid"), userName)
	} else {
		return h.db.Authenticator().DeleteSession(h.PathVar("sessionid"))
	}
}

// ADMIN API: Deletes all sessions for a user
func (h *handler) deleteUserSessions() error {
	h.assertAdminOnly()

	userName := h.PathVar("name")
	return h.db.DeleteUserSessions(userName)
}

// Delete a session if associated with the user provided
func (h *handler) deleteUserSessionWithValidation(sessionId string, userName string) error {

	// Validate that the session being deleted belongs to the user.  This adds some
	// overhead - for user-agnostic session deletion should use deleteSession
	session, getErr := h.db.Authenticator().GetSession(sessionId)
	if session == nil {
		if getErr == nil {
			getErr = kNotFoundError
		}
		return getErr
	}

	if getErr == nil {
		if session.Username == userName {
			delErr := h.db.Authenticator().DeleteSession(sessionId)
			if delErr != nil {
				return delErr
			}
		} else {
			return kNotFoundError
		}
	}
	return nil
}

// Respond with a JSON struct containing info about the current login session
func (h *handler) respondWithSessionInfoForSession(session *auth.LoginSession) error {

	user, err := h.db.Authenticator().GetUser(session.Username)

	// let the empty user case succeed
	if err != nil {
		return err
	}

	response := h.formatSessionResponse(user)
	if response != nil {
		h.writeJSON(response)
	}
	return nil
}

// Formats session response similar to what is returned by CouchDB
func (h *handler) formatSessionResponse(user auth.User) db.Body {

	var name *string
	allChannels := channels.TimedSet{}

	if user != nil {
		userName := user.Name()
		if userName != "" {
			name = &userName
		}
		allChannels = user.Channels()
	}

	// Return a JSON struct similar to what CouchDB returns:
	userCtx := db.Body{"name": name, "channels": allChannels}
	handlers := []string{"default", "cookie"}
	response := db.Body{"ok": true, "userCtx": userCtx, "authentication_handlers": handlers}
	return response

}
<commit_msg>configurable logout with CORS<commit_after>//  Copyright (c) 2013 Couchbase, Inc.
//  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 rest

import (
	"net/http"
	"time"

	"github.com/couchbase/sync_gateway/auth"
	"github.com/couchbase/sync_gateway/base"
	"github.com/couchbase/sync_gateway/channels"
	"github.com/couchbase/sync_gateway/db"
)

const kDefaultSessionTTL = 24 * time.Hour

// Respond with a JSON struct containing info about the current login session
func (h *handler) respondWithSessionInfo() error {

	response := h.formatSessionResponse(h.user)

	h.writeJSON(response)
	return nil
}

// GET /_session returns info about the current user
func (h *handler) handleSessionGET() error {
	return h.respondWithSessionInfo()
}

// POST /_session creates a login session and sets its cookie
func (h *handler) handleSessionPOST() error {
	// CORS not allowed for login #115 #762
	originHeader := h.rq.Header["Origin"]
	if len(originHeader) > 0 {
		matched := ""
		if h.server.config.CORS != nil {
			matched = matchedOrigin(h.server.config.CORS.LoginOrigin, originHeader)
		}
		if matched == "" {
			return base.HTTPErrorf(http.StatusBadRequest, "No CORS")
		}
	}

	user, err := h.getUserFromSessionRequestBody()

	// If we fail to get a user from the body and we've got a non-GUEST authenticated user, create the session based on that user
	if user == nil && h.user != nil && h.user.Name() != "" {
		return h.makeSession(h.user)
	} else {
		if err != nil {
			return err
		}
		return h.makeSession(user)
	}

}

func (h *handler) getUserFromSessionRequestBody() (auth.User, error) {

	var params struct {
		Name     string `json:"name"`
		Password string `json:"password"`
	}
	err := h.readJSONInto(&params)
	if err != nil {
		return nil, err
	}

	var user auth.User
	user, err = h.db.Authenticator().GetUser(params.Name)
	if err != nil {
		return nil, err
	}

	if user != nil && !user.Authenticate(params.Password) {
		user = nil
	}
	return user, err
}

// DELETE /_session logs out the current session
func (h *handler) handleSessionDELETE() error {
	// CORS not allowed for login #115 #762
	originHeader := h.rq.Header["Origin"]
	if len(originHeader) > 0 {
		matched := ""
		if h.server.config.CORS != nil {
			matched = matchedOrigin(h.server.config.CORS.LoginOrigin, originHeader)
		}
		if matched == "" {
			return base.HTTPErrorf(http.StatusBadRequest, "No CORS")
		}
	}

	cookie := h.db.Authenticator().DeleteSessionForCookie(h.rq)
	if cookie == nil {
		return base.HTTPErrorf(http.StatusNotFound, "no session")
	}
	http.SetCookie(h.response, cookie)
	return nil
}

func (h *handler) makeSession(user auth.User) error {

	_, err := h.makeSessionWithTTL(user, kDefaultSessionTTL)
	if err != nil {
		return err
	}
	return h.respondWithSessionInfo()
}

// Creates a session with TTL and adds to the response.  Does NOT return the session info response.
func (h *handler) makeSessionWithTTL(user auth.User, expiry time.Duration) (sessionID string, err error) {
	if user == nil {
		return "", base.HTTPErrorf(http.StatusUnauthorized, "Invalid login")
	}
	h.user = user
	auth := h.db.Authenticator()
	session, err := auth.CreateSession(user.Name(), expiry)
	if err != nil {
		return "", err
	}
	cookie := auth.MakeSessionCookie(session)
	base.AddDbPathToCookie(h.rq, cookie)
	http.SetCookie(h.response, cookie)
	return session.ID, nil
}

func (h *handler) makeSessionFromEmail(email string, createUserIfNeeded bool) error {

	// Email is verified. Look up the user and make a login session for her:
	user, err := h.db.Authenticator().GetUserByEmail(email)
	if err != nil {
		return err
	}
	if user == nil {
		// The email address is authentic but we have no user account for it.
		if !createUserIfNeeded {
			return base.HTTPErrorf(http.StatusUnauthorized, "No such user")
		}

		if len(email) < 1 {
			return base.HTTPErrorf(http.StatusBadRequest, "Cannot register new user: email is missing")
		}

		// Create a User with the given email address as username and a random password.
		user, err = h.db.Authenticator().RegisterNewUser(email, email)
		if err != nil {
			return err
		}
	}
	return h.makeSession(user)

}

// MakeSessionFromUserAndEmail first attempts to find the user by username.  If found, updates the users's
// email if different. If no match for username, attempts to find by the user by email.
// If not found, and createUserIfNeeded=true, creates a new user based on username, email.
func (h *handler) makeSessionFromNameAndEmail(username, email string, createUserIfNeeded bool) error {

	// Username and email are verified. Look up the user and make a login session for her - first
	// attempt lookup by name
	user, err := h.db.Authenticator().GetUser(username)
	if err != nil {
		return err
	}

	// If user found, check whether the email needs to be updated (e.g. user has changed email in
	// external auth system)
	if user != nil {
		if email != user.Email() {
			if err := user.SetEmail(email); err == nil {
				h.db.Authenticator().Save(user)
			}
		}
	} else {
		// User not found by name.  Attempt user lookup by email.  This provides backward
		// compatibility for users that were originally created with id = email
		user, err = h.db.Authenticator().GetUserByEmail(email)
		if err != nil {
			return err
		}
	}
	if user == nil {
		// The user/email are validated, but we don't have a user for either
		if !createUserIfNeeded {
			return base.HTTPErrorf(http.StatusUnauthorized, "No such user")
		}

		if len(email) < 1 {
			return base.HTTPErrorf(http.StatusBadRequest, "Cannot register new user: email is missing")
		}

		// Create a User with the given email address as username and a random password.
		user, err = h.db.Authenticator().RegisterNewUser(username, email)
		if err != nil {
			return err
		}
	}
	return h.makeSession(user)
}

// ADMIN API: Generates a login session for a user and returns the session ID and cookie name.
func (h *handler) createUserSession() error {
	h.assertAdminOnly()
	var params struct {
		Name string `json:"name"`
		TTL  int    `json:"ttl"`
	}
	params.TTL = int(kDefaultSessionTTL / time.Second)
	err := h.readJSONInto(&params)
	if err != nil {
		return err
	} else if params.Name == "" || params.Name == base.GuestUsername || !auth.IsValidPrincipalName(params.Name) {
		return base.HTTPErrorf(http.StatusBadRequest, "Invalid or missing user name")
	} else if user, err := h.db.Authenticator().GetUser(params.Name); user == nil {
		if err == nil {
			err = base.HTTPErrorf(http.StatusNotFound, "No such user %q", params.Name)
		}
		return err
	}

	ttl := time.Duration(params.TTL) * time.Second
	if ttl < 1.0 {
		return base.HTTPErrorf(http.StatusBadRequest, "Invalid or missing ttl")
	}

	session, err := h.db.Authenticator().CreateSession(params.Name, ttl)
	if err != nil {
		return err
	}
	var response struct {
		SessionID  string    `json:"session_id"`
		Expires    time.Time `json:"expires"`
		CookieName string    `json:"cookie_name"`
	}
	response.SessionID = session.ID
	response.Expires = session.Expiration
	response.CookieName = auth.CookieName
	h.writeJSON(response)
	return nil
}

func (h *handler) getUserSession() error {

	h.assertAdminOnly()
	session, err := h.db.Authenticator().GetSession(h.PathVar("sessionid"))

	if session == nil {
		if err == nil {
			err = kNotFoundError
		}
		return err
	}

	return h.respondWithSessionInfoForSession(session)
}

// ADMIN API: Deletes a specified session.  If username is present on the request, validates
// that the session being deleted is associated with the user.
func (h *handler) deleteUserSession() error {
	h.assertAdminOnly()
	userName := h.PathVar("name")
	if userName != "" {
		return h.deleteUserSessionWithValidation(h.PathVar("sessionid"), userName)
	} else {
		return h.db.Authenticator().DeleteSession(h.PathVar("sessionid"))
	}
}

// ADMIN API: Deletes all sessions for a user
func (h *handler) deleteUserSessions() error {
	h.assertAdminOnly()

	userName := h.PathVar("name")
	return h.db.DeleteUserSessions(userName)
}

// Delete a session if associated with the user provided
func (h *handler) deleteUserSessionWithValidation(sessionId string, userName string) error {

	// Validate that the session being deleted belongs to the user.  This adds some
	// overhead - for user-agnostic session deletion should use deleteSession
	session, getErr := h.db.Authenticator().GetSession(sessionId)
	if session == nil {
		if getErr == nil {
			getErr = kNotFoundError
		}
		return getErr
	}

	if getErr == nil {
		if session.Username == userName {
			delErr := h.db.Authenticator().DeleteSession(sessionId)
			if delErr != nil {
				return delErr
			}
		} else {
			return kNotFoundError
		}
	}
	return nil
}

// Respond with a JSON struct containing info about the current login session
func (h *handler) respondWithSessionInfoForSession(session *auth.LoginSession) error {

	user, err := h.db.Authenticator().GetUser(session.Username)

	// let the empty user case succeed
	if err != nil {
		return err
	}

	response := h.formatSessionResponse(user)
	if response != nil {
		h.writeJSON(response)
	}
	return nil
}

// Formats session response similar to what is returned by CouchDB
func (h *handler) formatSessionResponse(user auth.User) db.Body {

	var name *string
	allChannels := channels.TimedSet{}

	if user != nil {
		userName := user.Name()
		if userName != "" {
			name = &userName
		}
		allChannels = user.Channels()
	}

	// Return a JSON struct similar to what CouchDB returns:
	userCtx := db.Body{"name": name, "channels": allChannels}
	handlers := []string{"default", "cookie"}
	response := db.Body{"ok": true, "userCtx": userCtx, "authentication_handlers": handlers}
	return response

}
<|endoftext|>