<commit_before>package main

import (
	"bufio"
	"errors"
	"flag"
	"fmt"
	"github.com/Shopify/themekit"
	"github.com/Shopify/themekit/commands"
	"os"
	"path/filepath"
	"strings"
	"time"
)

const banner string = "----------------------------------------"
const updateAvailableMessage string = `| An update for Theme Kit is available |
|                                      |
| To apply the update simply type      |
| the following command:               |
|                                      |
| theme update                         |`

const commandDefault string = "download [<file> ...]"

var globalEventLog chan themekit.ThemeEvent

var permittedZeroArgCommands = map[string]bool{
	"upload":   true,
	"download": true,
	"replace":  true,
	"watch":    true,
	"version":  true,
	"update":   true,
}

var commandDescriptionPrefix = []string{
	"An operation to be performed against the theme.",
	"  Valid commands are:",
}

var permittedCommands = map[string]string{
	"upload <file> [<file2> ...]": "Add file(s) to theme",
	"download [<file> ...]":       "Download file(s) from theme",
	"remove <file> [<file2> ...]": "Remove file(s) from theme",
	"replace [<file> ...]":        "Overwrite theme file(s)",
	"watch":                       "Watch directory for changes and update remote theme",
	"configure":                   "Create a configuration file",
	"bootstrap":                   "Bootstrap a new theme using Shopify Timber",
	"version":                     "Display themekit version",
	"update":                      "Update application",
}

type CommandParser func(string, []string) (map[string]interface{}, *flag.FlagSet)

var parserMapping = map[string]CommandParser{
	"upload":    FileManipulationCommandParser,
	"download":  FileManipulationCommandParser,
	"remove":    FileManipulationCommandParser,
	"replace":   FileManipulationCommandParser,
	"watch":     WatchCommandParser,
	"configure": ConfigurationCommandParser,
	"bootstrap": BootstrapParser,
	"version":   NoOpParser,
	"update":    NoOpParser,
}

type Command func(map[string]interface{}) chan bool

var commandMapping = map[string]Command{
	"upload":    commands.UploadCommand,
	"download":  commands.DownloadCommand,
	"remove":    commands.RemoveCommand,
	"replace":   commands.ReplaceCommand,
	"watch":     commands.WatchCommand,
	"configure": commands.ConfigureCommand,
	"bootstrap": commands.BootstrapCommand,
	"version":   commands.VersionCommand,
	"update":    commands.UpdateCommand,
}

func CommandDescription(defaultCommand string) string {
	commandDescription := make([]string, len(commandDescriptionPrefix)+len(permittedCommands))
	pos := 0
	for i := range commandDescriptionPrefix {
		commandDescription[pos] = commandDescriptionPrefix[i]
		pos++
	}

	for cmd, desc := range permittedCommands {
		def := ""
		if cmd == defaultCommand {
			def = " [default]"
		}
		commandDescription[pos] = fmt.Sprintf("    %s:\n        %s%s", cmd, desc, def)
		pos++
	}

	return strings.Join(commandDescription, "\n")
}

func setupErrorReporter() {
	themekit.SetErrorReporter(themekit.HaltExecutionReporter{})
}

func setupGlobalEventLog() {
	globalEventLog = make(chan themekit.ThemeEvent)
}

func checkForUpdate() {
	if commands.IsNewReleaseAvailable() {
		message := fmt.Sprintf("%s\n%s\n%s", banner, updateAvailableMessage, banner)
		fmt.Println(themekit.YellowText(message))
	}
}

func main() {
	setupGlobalEventLog()
	setupErrorReporter()
	command, rest := SetupAndParseArgs(os.Args[1:])
	verifyCommand(command, rest)
	if command != "update" {
		go checkForUpdate()
	}

	args, _ := parserMapping[command](command, rest)
	args["eventLog"] = globalEventLog

	operation := commandMapping[command]
	done := operation(args)
	output := bufio.NewWriter(os.Stdout)
	go func() {
		ticked := false
		for {
			select {
			case event := <-globalEventLog:
				if len(event.String()) > 0 {
					output.Write([]byte(fmt.Sprintf("%s\n", event)))
					output.Flush()
				}
			case <-time.Tick(1000 * time.Millisecond):
				if !ticked {
					ticked = true
					done <- true
				}
			}
		}
	}()
	<-done
	<-done
	output.Flush()
}

func NoOpParser(cmd string, args []string) (result map[string]interface{}, set *flag.FlagSet) {
	return make(map[string]interface{}), nil
}

func FileManipulationCommandParser(cmd string, args []string) (result map[string]interface{}, set *flag.FlagSet) {
	result = make(map[string]interface{})
	currentDir, _ := os.Getwd()
	var environment, directory string

	set = makeFlagSet(cmd)
	set.StringVar(&environment, "env", themekit.DefaultEnvironment, "environment to run command")
	set.StringVar(&directory, "dir", currentDir, "directory that config.yml is located")
	set.Parse(args)

	result["themeClient"] = loadThemeClient(directory, environment)
	result["filenames"] = args[len(args)-set.NArg():]
	return
}

func WatchCommandParser(cmd string, args []string) (result map[string]interface{}, set *flag.FlagSet) {
	result = make(map[string]interface{})
	currentDir, _ := os.Getwd()
	var allEnvironments bool
	var environment, directory, notifyFile string
	var environments themekit.Environments

	set = makeFlagSet(cmd)
	set.StringVar(&environment, "env", themekit.DefaultEnvironment, "environment to run command")
	set.BoolVar(&allEnvironments, "allenvs", false, "start watchers for all environments")
	set.StringVar(&directory, "dir", currentDir, "directory that config.yml is located")
	set.StringVar(&notifyFile, "notify", "", "file to touch when workers have gone idle")
	set.Parse(args)

	if len(environment) != 0 && allEnvironments {
		environment = ""
	}

	result["notifyFile"] = notifyFile
	environments, err := loadEnvironments(directory)
	if allEnvironments && err == nil {
		result["environments"] = environments
	}

	result["themeClient"] = loadThemeClient(directory, environment)
	return
}

func ConfigurationCommandParser(cmd string, args []string) (result map[string]interface{}, set *flag.FlagSet) {
	result = make(map[string]interface{})
	currentDir, _ := os.Getwd()
	var directory, environment, domain, accessToken string
	var bucketSize, refillRate int

	set = makeFlagSet(cmd)
	set.StringVar(&directory, "dir", currentDir, "directory to create config.yml")
	set.StringVar(&environment, "env", themekit.DefaultEnvironment, "environment for this configuration")
	set.StringVar(&domain, "domain", "", "your myshopify domain")
	set.StringVar(&accessToken, "access_token", "", "accessToken (or password) to make successful API calls")
	set.IntVar(&bucketSize, "bucketSize", themekit.DefaultBucketSize, "leaky bucket capacity")
	set.IntVar(&refillRate, "refillRate", themekit.DefaultRefillRate, "leaky bucket refill rate / second")
	set.Parse(args)

	result["directory"] = directory
	result["environment"] = environment
	result["domain"] = domain
	result["access_token"] = accessToken
	result["bucket_size"] = bucketSize
	result["refill_rate"] = refillRate
	return
}

func BootstrapParser(cmd string, args []string) (result map[string]interface{}, set *flag.FlagSet) {
	result = make(map[string]interface{})
	currentDir, _ := os.Getwd()
	var version, directory, environment, prefix string
	var setThemeId bool

	set = makeFlagSet(cmd)
	set.StringVar(&directory, "dir", currentDir, "location of config.yml")
	set.BoolVar(&setThemeId, "setid", true, "update config.yml with ID of created Theme")
	set.StringVar(&environment, "env", themekit.DefaultEnvironment, "environment to execute command")
	set.StringVar(&version, "version", commands.LatestRelease, "version of Shopify Timber to use")
	set.StringVar(&prefix, "prefix", "", "prefix to the Timber theme being created")
	set.Parse(args)

	result["version"] = version
	result["directory"] = directory
	result["environment"] = environment
	result["prefix"] = prefix
	result["setThemeId"] = setThemeId
	result["themeClient"] = loadThemeClient(directory, environment)
	return
}

func loadThemeClient(directory, env string) themekit.ThemeClient {
	client, err := loadThemeClientWithRetry(directory, env, false)
	handleError(err)
	return client
}

func loadThemeClientWithRetry(directory, env string, isRetry bool) (themekit.ThemeClient, error) {
	environments, err := loadEnvironments(directory)
	if err != nil {
		return themekit.ThemeClient{}, err
	}
	config, err := environments.GetConfiguration(env)
	if err != nil && len(environments) > 0 {
		invalidEnvMsg := fmt.Sprintf("'%s' is not a valid environment. The following environments are available within config.yml:", env)
		fmt.Println(themekit.RedText(invalidEnvMsg))
		for e, _ := range environments {
			fmt.Println(themekit.RedText(fmt.Sprintf(" - %s", e)))
		}
		os.Exit(1)
	} else if err != nil && !isRetry {
		upgradeMessage := fmt.Sprintf("Looks like your configuration file is out of date. Upgrading to default environment '%s'", themekit.DefaultEnvironment)
		fmt.Println(themekit.YellowText(upgradeMessage))
		confirmationfn, savefn := commands.PrepareConfigurationMigration(directory)

		if confirmationfn() && savefn() == nil {
			return loadThemeClientWithRetry(directory, env, true)
		}

		return themekit.ThemeClient{}, errors.New("loadThemeClientWithRetry: could not load or migrate the configuration")
	} else if err != nil {
		return themekit.ThemeClient{}, err
	}

	return themekit.NewThemeClient(config), nil
}

func loadEnvironments(directory string) (themekit.Environments, error) {
	return themekit.LoadEnvironmentsFromFile(filepath.Join(directory, "config.yml"))
}

func SetupAndParseArgs(args []string) (command string, rest []string) {
	set := makeFlagSet("")
	set.Usage = func() {
		fmt.Println(CommandDescription(commandDefault))
	}
	set.Parse(args)

	command = args[0]
	rest = args[1:]
	return
}

func CommandIsInvalid(command string) bool {
	return commandMapping[command] == nil
}

func CannotProcessCommandWithoutAdditionalArguments(command string, additionalArgs []string) bool {
	return len(additionalArgs) <= 0 && permittedZeroArgCommands[command] == false
}

func verifyCommand(command string, args []string) {
	errors := []string{}

	if CommandIsInvalid(command) {
		errors = append(errors, fmt.Sprintf("\t-'%s' is not a valid command", command))
	}

	if CannotProcessCommandWithoutAdditionalArguments(command, args) {
		if parser, ok := parserMapping[command]; ok {
			errors = append(errors, fmt.Sprintf("\t- '%s' cannot run without additional arguments", command))
			parser(command, []string{"-h"})
		}
	}

	if len(errors) > 0 {
		errorMessage := fmt.Sprintf("Invalid Invocation!\n%s", strings.Join(errors, "\n"))
		fmt.Println(themekit.RedText(errorMessage))
		SetupAndParseArgs([]string{"--help"})
		os.Exit(1)
	}
}

func makeFlagSet(command string) *flag.FlagSet {
	if command != "" {
		command = " " + command
	}
	return flag.NewFlagSet(fmt.Sprintf("theme%s", command), flag.ExitOnError)
}

func handleError(err error) {
	if err == nil {
		return
	}

	if strings.Contains(err.Error(), "YAML error") {
		err = errors.New(fmt.Sprintf("configuration error: does your configuration properly escape wildcards? \n\t\t\t%s", err))
	} else if strings.Contains(err.Error(), "no such file or directory") {
		err = errors.New(fmt.Sprintf("configuration error: %s", err))
	}
	themekit.NotifyErrorImmediately(err)
}
<commit_msg>Ensure default command<commit_after>package main

import (
	"bufio"
	"errors"
	"flag"
	"fmt"
	"github.com/Shopify/themekit"
	"github.com/Shopify/themekit/commands"
	"os"
	"path/filepath"
	"strings"
	"time"
)

const banner string = "----------------------------------------"
const updateAvailableMessage string = `| An update for Theme Kit is available |
|                                      |
| To apply the update simply type      |
| the following command:               |
|                                      |
| theme update                         |`

const commandDefault string = "download [<file> ...]"
const defaultCommand string = "download"

var globalEventLog chan themekit.ThemeEvent

var permittedZeroArgCommands = map[string]bool{
	"upload":   true,
	"download": true,
	"replace":  true,
	"watch":    true,
	"version":  true,
	"update":   true,
}

var commandDescriptionPrefix = []string{
	"An operation to be performed against the theme.",
	"  Valid commands are:",
}

var permittedCommands = map[string]string{
	"upload <file> [<file2> ...]": "Add file(s) to theme",
	"download [<file> ...]":       "Download file(s) from theme",
	"remove <file> [<file2> ...]": "Remove file(s) from theme",
	"replace [<file> ...]":        "Overwrite theme file(s)",
	"watch":                       "Watch directory for changes and update remote theme",
	"configure":                   "Create a configuration file",
	"bootstrap":                   "Bootstrap a new theme using Shopify Timber",
	"version":                     "Display themekit version",
	"update":                      "Update application",
}

type CommandParser func(string, []string) (map[string]interface{}, *flag.FlagSet)

var parserMapping = map[string]CommandParser{
	"upload":    FileManipulationCommandParser,
	"download":  FileManipulationCommandParser,
	"remove":    FileManipulationCommandParser,
	"replace":   FileManipulationCommandParser,
	"watch":     WatchCommandParser,
	"configure": ConfigurationCommandParser,
	"bootstrap": BootstrapParser,
	"version":   NoOpParser,
	"update":    NoOpParser,
}

type Command func(map[string]interface{}) chan bool

var commandMapping = map[string]Command{
	"upload":    commands.UploadCommand,
	"download":  commands.DownloadCommand,
	"remove":    commands.RemoveCommand,
	"replace":   commands.ReplaceCommand,
	"watch":     commands.WatchCommand,
	"configure": commands.ConfigureCommand,
	"bootstrap": commands.BootstrapCommand,
	"version":   commands.VersionCommand,
	"update":    commands.UpdateCommand,
}

func CommandDescription(defaultCommand string) string {
	commandDescription := make([]string, len(commandDescriptionPrefix)+len(permittedCommands))
	pos := 0
	for i := range commandDescriptionPrefix {
		commandDescription[pos] = commandDescriptionPrefix[i]
		pos++
	}

	for cmd, desc := range permittedCommands {
		def := ""
		if cmd == defaultCommand {
			def = " [default]"
		}
		commandDescription[pos] = fmt.Sprintf("    %s:\n        %s%s", cmd, desc, def)
		pos++
	}

	return strings.Join(commandDescription, "\n")
}

func setupErrorReporter() {
	themekit.SetErrorReporter(themekit.HaltExecutionReporter{})
}

func setupGlobalEventLog() {
	globalEventLog = make(chan themekit.ThemeEvent)
}

func checkForUpdate() {
	if commands.IsNewReleaseAvailable() {
		message := fmt.Sprintf("%s\n%s\n%s", banner, updateAvailableMessage, banner)
		fmt.Println(themekit.YellowText(message))
	}
}

func main() {
	setupGlobalEventLog()
	setupErrorReporter()
	command, rest := SetupAndParseArgs(os.Args[1:])
	verifyCommand(command, rest)
	if command != "update" {
		go checkForUpdate()
	}

	args, _ := parserMapping[command](command, rest)
	args["eventLog"] = globalEventLog

	operation := commandMapping[command]
	done := operation(args)
	output := bufio.NewWriter(os.Stdout)
	go func() {
		ticked := false
		for {
			select {
			case event := <-globalEventLog:
				if len(event.String()) > 0 {
					output.Write([]byte(fmt.Sprintf("%s\n", event)))
					output.Flush()
				}
			case <-time.Tick(1000 * time.Millisecond):
				if !ticked {
					ticked = true
					done <- true
				}
			}
		}
	}()
	<-done
	<-done
	output.Flush()
}

func NoOpParser(cmd string, args []string) (result map[string]interface{}, set *flag.FlagSet) {
	return make(map[string]interface{}), nil
}

func FileManipulationCommandParser(cmd string, args []string) (result map[string]interface{}, set *flag.FlagSet) {
	result = make(map[string]interface{})
	currentDir, _ := os.Getwd()
	var environment, directory string

	set = makeFlagSet(cmd)
	set.StringVar(&environment, "env", themekit.DefaultEnvironment, "environment to run command")
	set.StringVar(&directory, "dir", currentDir, "directory that config.yml is located")
	set.Parse(args)

	result["themeClient"] = loadThemeClient(directory, environment)
	result["filenames"] = args[len(args)-set.NArg():]
	return
}

func WatchCommandParser(cmd string, args []string) (result map[string]interface{}, set *flag.FlagSet) {
	result = make(map[string]interface{})
	currentDir, _ := os.Getwd()
	var allEnvironments bool
	var environment, directory, notifyFile string
	var environments themekit.Environments

	set = makeFlagSet(cmd)
	set.StringVar(&environment, "env", themekit.DefaultEnvironment, "environment to run command")
	set.BoolVar(&allEnvironments, "allenvs", false, "start watchers for all environments")
	set.StringVar(&directory, "dir", currentDir, "directory that config.yml is located")
	set.StringVar(&notifyFile, "notify", "", "file to touch when workers have gone idle")
	set.Parse(args)

	if len(environment) != 0 && allEnvironments {
		environment = ""
	}

	result["notifyFile"] = notifyFile
	environments, err := loadEnvironments(directory)
	if allEnvironments && err == nil {
		result["environments"] = environments
	}

	result["themeClient"] = loadThemeClient(directory, environment)
	return
}

func ConfigurationCommandParser(cmd string, args []string) (result map[string]interface{}, set *flag.FlagSet) {
	result = make(map[string]interface{})
	currentDir, _ := os.Getwd()
	var directory, environment, domain, accessToken string
	var bucketSize, refillRate int

	set = makeFlagSet(cmd)
	set.StringVar(&directory, "dir", currentDir, "directory to create config.yml")
	set.StringVar(&environment, "env", themekit.DefaultEnvironment, "environment for this configuration")
	set.StringVar(&domain, "domain", "", "your myshopify domain")
	set.StringVar(&accessToken, "access_token", "", "accessToken (or password) to make successful API calls")
	set.IntVar(&bucketSize, "bucketSize", themekit.DefaultBucketSize, "leaky bucket capacity")
	set.IntVar(&refillRate, "refillRate", themekit.DefaultRefillRate, "leaky bucket refill rate / second")
	set.Parse(args)

	result["directory"] = directory
	result["environment"] = environment
	result["domain"] = domain
	result["access_token"] = accessToken
	result["bucket_size"] = bucketSize
	result["refill_rate"] = refillRate
	return
}

func BootstrapParser(cmd string, args []string) (result map[string]interface{}, set *flag.FlagSet) {
	result = make(map[string]interface{})
	currentDir, _ := os.Getwd()
	var version, directory, environment, prefix string
	var setThemeId bool

	set = makeFlagSet(cmd)
	set.StringVar(&directory, "dir", currentDir, "location of config.yml")
	set.BoolVar(&setThemeId, "setid", true, "update config.yml with ID of created Theme")
	set.StringVar(&environment, "env", themekit.DefaultEnvironment, "environment to execute command")
	set.StringVar(&version, "version", commands.LatestRelease, "version of Shopify Timber to use")
	set.StringVar(&prefix, "prefix", "", "prefix to the Timber theme being created")
	set.Parse(args)

	result["version"] = version
	result["directory"] = directory
	result["environment"] = environment
	result["prefix"] = prefix
	result["setThemeId"] = setThemeId
	result["themeClient"] = loadThemeClient(directory, environment)
	return
}

func loadThemeClient(directory, env string) themekit.ThemeClient {
	client, err := loadThemeClientWithRetry(directory, env, false)
	handleError(err)
	return client
}

func loadThemeClientWithRetry(directory, env string, isRetry bool) (themekit.ThemeClient, error) {
	environments, err := loadEnvironments(directory)
	if err != nil {
		return themekit.ThemeClient{}, err
	}
	config, err := environments.GetConfiguration(env)
	if err != nil && len(environments) > 0 {
		invalidEnvMsg := fmt.Sprintf("'%s' is not a valid environment. The following environments are available within config.yml:", env)
		fmt.Println(themekit.RedText(invalidEnvMsg))
		for e, _ := range environments {
			fmt.Println(themekit.RedText(fmt.Sprintf(" - %s", e)))
		}
		os.Exit(1)
	} else if err != nil && !isRetry {
		upgradeMessage := fmt.Sprintf("Looks like your configuration file is out of date. Upgrading to default environment '%s'", themekit.DefaultEnvironment)
		fmt.Println(themekit.YellowText(upgradeMessage))
		confirmationfn, savefn := commands.PrepareConfigurationMigration(directory)

		if confirmationfn() && savefn() == nil {
			return loadThemeClientWithRetry(directory, env, true)
		}

		return themekit.ThemeClient{}, errors.New("loadThemeClientWithRetry: could not load or migrate the configuration")
	} else if err != nil {
		return themekit.ThemeClient{}, err
	}

	return themekit.NewThemeClient(config), nil
}

func loadEnvironments(directory string) (themekit.Environments, error) {
	return themekit.LoadEnvironmentsFromFile(filepath.Join(directory, "config.yml"))
}

func SetupAndParseArgs(args []string) (command string, rest []string) {
	if len(args) <= 0 {
		return defaultCommand, []string{}
	}
	set := makeFlagSet("")
	set.Usage = func() {
		fmt.Println(CommandDescription(commandDefault))
	}
	set.Parse(args)

	command = args[0]
	rest = args[1:]
	return
}

func CommandIsInvalid(command string) bool {
	return commandMapping[command] == nil
}

func CannotProcessCommandWithoutAdditionalArguments(command string, additionalArgs []string) bool {
	return len(additionalArgs) <= 0 && permittedZeroArgCommands[command] == false
}

func verifyCommand(command string, args []string) {
	errors := []string{}

	if CommandIsInvalid(command) {
		errors = append(errors, fmt.Sprintf("\t-'%s' is not a valid command", command))
	}

	if CannotProcessCommandWithoutAdditionalArguments(command, args) {
		if parser, ok := parserMapping[command]; ok {
			errors = append(errors, fmt.Sprintf("\t- '%s' cannot run without additional arguments", command))
			parser(command, []string{"-h"})
		}
	}

	if len(errors) > 0 {
		errorMessage := fmt.Sprintf("Invalid Invocation!\n%s", strings.Join(errors, "\n"))
		fmt.Println(themekit.RedText(errorMessage))
		SetupAndParseArgs([]string{"--help"})
		os.Exit(1)
	}
}

func makeFlagSet(command string) *flag.FlagSet {
	if command != "" {
		command = " " + command
	}
	return flag.NewFlagSet(fmt.Sprintf("theme%s", command), flag.ExitOnError)
}

func handleError(err error) {
	if err == nil {
		return
	}

	if strings.Contains(err.Error(), "YAML error") {
		err = errors.New(fmt.Sprintf("configuration error: does your configuration properly escape wildcards? \n\t\t\t%s", err))
	} else if strings.Contains(err.Error(), "no such file or directory") {
		err = errors.New(fmt.Sprintf("configuration error: %s", err))
	}
	themekit.NotifyErrorImmediately(err)
}
<|endoftext|>


<commit_before>// Copyright 2012-2017 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// assumptions
// we've been booted into a ramfs with all this stuff unpacked and ready.
// we don't need a loop device mount because it's all there.
// So we run /go/bin/go build installcommand
// and then exec /buildbin/sh

package main

import (
	"flag"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path"
	"strings"
	"syscall"

	"github.com/u-root/u-root/uroot"
)

var (
	verbose   = flag.Bool("v", false, "print all build commands")
	ludicrous = flag.Bool("ludicrous", false, "print out information about symlink creation")
	debug     = func(string, ...interface{}) {}
)

func main() {
	a := []string{"build"}
	flag.Parse()
	log.Printf("Welcome to u-root")
	uroot.Rootfs()

	if *verbose {
		debug = log.Printf
		a = append(a, "-x")
	}

	// populate buildbin

	// In earlier versions we just had src/cmds. Due to the Go rules it seems we need to
	// embed the URL of the repo everywhere. Yuck.
	if commands, err := ioutil.ReadDir(path.Join("/src", uroot.CmdsPath)); err == nil {
		for _, v := range commands {
			name := v.Name()
			if name == "installcommand" || name == "init" {
				continue
			} else {
				destPath := path.Join("/buildbin", name)
				source := "/buildbin/installcommand"
				if err := os.Symlink(source, destPath); err != nil {
					log.Printf("Symlink %v -> %v failed; %v", source, destPath, err)
				}
				if *ludicrous {
					log.Printf("Symlink %v -> %v", source, destPath)
				}
			}
		}
	} else {
		log.Fatalf("Can't read %v; %v", "/src", err)
	}
	envs := uroot.Envs
	debug("envs %v", envs)
	os.Setenv("GOBIN", "/buildbin")
	a = append(a, "-o", "/buildbin/installcommand", path.Join(uroot.CmdsPath, "installcommand"))
	cmd := exec.Command("go", a...)
	installenvs := envs
	installenvs = append(envs, "GOBIN=/buildbin")
	cmd.Env = installenvs
	cmd.Dir = "/"

	cmd.Stdin = os.Stdin
	cmd.Stderr = os.Stderr
	cmd.Stdout = os.Stdout
	debug("Run %v", cmd)
	if err := cmd.Run(); err != nil {
		log.Printf("%v\n", err)
	}

	// install /env.
	os.Setenv("GOBIN", "/ubin")
	envs = append(envs, "GOBIN=/ubin")
	for _, e := range envs {
		nv := strings.SplitN(e, "=", 2)
		if len(nv) < 2 {
			nv = append(nv, "")
		}
		n := path.Join("/env", nv[0])
		if err := ioutil.WriteFile(n, []byte(nv[1]), 0666); err != nil {
			log.Printf("%v: %v", n, err)
		}
	}

	// Now here's some good fun. We've set environment variables we want to see used.
	// But on some systems the environment variable we create is completely ignored.
	// Oh, is that you again, tinycore? Well.
	// So we can save the day by writing the uroot.profile string to /etc/profile.d/uroot.sh
	// mode, usually, 644.
	// Only bother doing this is /etc/profile.d exists and is a directory.
	if fi, err := os.Stat("/etc/profile.d"); err == nil && fi.IsDir() {
		if err := ioutil.WriteFile("/etc/profile.d/uroot.sh", []byte(uroot.Profile), 0644); err != nil {
			log.Printf("Trying to write uroot profile failed: %v", err)
		}
	}

	// There may be an inito if we are building on
	// an existing initramfs. So, first, try to
	// run inito and then run our shell
	// Perhaps we should stat inito first.
	// inito is always first and we set default flags for it.
	cloneFlags := uintptr(syscall.CLONE_NEWPID)
	for _, v := range []string{"/inito", "/buildbin/rush"} {
		cmd = exec.Command(v)
		cmd.Env = envs
		cmd.Stdin = os.Stdin
		cmd.Stderr = os.Stderr
		cmd.Stdout = os.Stdout
		// TODO: figure out why we get EPERM when we use this.
		//cmd.SysProcAttr = &syscall.SysProcAttr{Setctty: true, Setsid: true, Cloneflags: cloneFlags}
		cmd.SysProcAttr = &syscall.SysProcAttr{Cloneflags: cloneFlags}
		debug("Run %v", cmd)
		if err := cmd.Run(); err != nil {
			log.Print(err)
		}
		// only the first init needs its own PID space.
		cloneFlags = 0
	}

	log.Printf("init: All commands exited")
	log.Printf("Rebooting...")
	err := exec.Command("shutdown", "reboot").Run()

	// Regardless of whether err is nil, if shutdown returns at all, it
	// failed at its job.
	log.Fatalf("Failed to reboot: %v", err)
}
<commit_msg>Revert "Installcommand exec"<commit_after>// Copyright 2012-2017 the u-root Authors. All rights reserved
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// assumptions
// we've been booted into a ramfs with all this stuff unpacked and ready.
// we don't need a loop device mount because it's all there.
// So we run /go/bin/go build installcommand
// and then exec /buildbin/sh

package main

import (
	"flag"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path"
	"strings"
	"syscall"

	"github.com/u-root/u-root/uroot"
)

var (
	verbose   = flag.Bool("v", false, "print all build commands")
	ludicrous = flag.Bool("ludicrous", false, "print out information about symlink creation")
	debug     = func(string, ...interface{}) {}
)

func main() {
	a := []string{"build"}
	flag.Parse()
	log.Printf("Welcome to u-root")
	uroot.Rootfs()

	if *verbose {
		debug = log.Printf
		a = append(a, "-x")
	}

	// populate buildbin

	// In earlier versions we just had src/cmds. Due to the Go rules it seems we need to
	// embed the URL of the repo everywhere. Yuck.
	if commands, err := ioutil.ReadDir(path.Join("/src", uroot.CmdsPath)); err == nil {
		for _, v := range commands {
			name := v.Name()
			if name == "installcommand" || name == "init" {
				continue
			} else {
				destPath := path.Join("/buildbin", name)
				source := "/buildbin/installcommand"
				if err := os.Symlink(source, destPath); err != nil {
					log.Printf("Symlink %v -> %v failed; %v", source, destPath, err)
				}
				if *ludicrous {
					log.Printf("Symlink %v -> %v", source, destPath)
				}
			}
		}
	} else {
		log.Fatalf("Can't read %v; %v", "/src", err)
	}
	envs := uroot.Envs
	debug("envs %v", envs)
	os.Setenv("GOBIN", "/buildbin")
	a = append(a, "-o", "/buildbin/installcommand", path.Join(uroot.CmdsPath, "installcommand"))
	cmd := exec.Command("go", a...)
	installenvs := envs
	installenvs = append(envs, "GOBIN=/buildbin")
	cmd.Env = installenvs
	cmd.Dir = "/"

	cmd.Stdin = os.Stdin
	cmd.Stderr = os.Stderr
	cmd.Stdout = os.Stdout
	debug("Run %v", cmd)
	if err := cmd.Run(); err != nil {
		log.Printf("%v\n", err)
	}

	// install /env.
	os.Setenv("GOBIN", "/ubin")
	envs = append(envs, "GOBIN=/ubin")
	for _, e := range envs {
		nv := strings.SplitN(e, "=", 2)
		if len(nv) < 2 {
			nv = append(nv, "")
		}
		n := path.Join("/env", nv[0])
		if err := ioutil.WriteFile(n, []byte(nv[1]), 0666); err != nil {
			log.Printf("%v: %v", n, err)
		}
	}

	// Now here's some good fun. We've set environment variables we want to see used.
	// But on some systems the environment variable we create is completely ignored.
	// Oh, is that you again, tinycore? Well.
	// So we can save the day by writing the uroot.profile string to /etc/profile.d/uroot.sh
	// mode, usually, 644.
	// Only bother doing this is /etc/profile.d exists and is a directory.
	if fi, err := os.Stat("/etc/profile.d"); err == nil && fi.IsDir() {
		if err := ioutil.WriteFile("/etc/profile.d/uroot.sh", []byte(uroot.Profile), 0644); err != nil {
			log.Printf("Trying to write uroot profile failed: %v", err)
		}
	}

	// There may be an inito if we are building on
	// an existing initramfs. So, first, try to
	// run inito and then run our shell
	// Perhaps we should stat inito first.
	// inito is always first and we set default flags for it.
	cloneFlags := uintptr(syscall.CLONE_NEWPID)
	for _, v := range []string{"/inito", "/buildbin/rush"} {
		cmd = exec.Command(v)
		cmd.Env = envs
		cmd.Stdin = os.Stdin
		cmd.Stderr = os.Stderr
		cmd.Stdout = os.Stdout
		// TODO: figure out why we get EPERM when we use this.
		//cmd.SysProcAttr = &syscall.SysProcAttr{Setctty: true, Setsid: true, Cloneflags: cloneFlags}
		cmd.SysProcAttr = &syscall.SysProcAttr{Cloneflags: cloneFlags}
		debug("Run %v", cmd)
		if err := cmd.Run(); err != nil {
			log.Printf("%v\n", err)
		}
		// only the first init needs its own PID space.
		cloneFlags = 0
	}
	log.Printf("init: All commands exited")
}
<|endoftext|>


<commit_before>// Copyright © 2015 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.

package cmd

import (
	"fmt"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
	"os"
	"path"
)

var (
	pkgName string

	initCmd = &cobra.Command{
		Use:     "init [name]",
		Aliases: []string{"initialize", "initialise", "create"},
		Short:   "Initialize a Cobra Application",
		Long: `Initialize (cobra init) will create a new application, with a license
and the appropriate structure for a Cobra-based CLI application.

  * If a name is provided, it will be created in the current directory;
  * If no name is provided, the current directory will be assumed;
  * If a relative path is provided, it will be created inside $GOPATH
    (e.g. github.com/spf13/hugo);
  * If an absolute path is provided, it will be created;
  * If the directory already exists but is empty, it will be used.

Init will not use an existing directory with contents.`,

		Run: func(cmd *cobra.Command, args []string) {

			wd, err := os.Getwd()
			if err != nil {
				er(err)
			}

			if len(args) > 0 {
				if args[0] != "." {
					wd = fmt.Sprintf("%s/%s", wd, args[0])
				}
			}

			project := &Project{
				AbsolutePath: wd,
				PkgName:      pkgName,
				Legal:        getLicense(),
				Copyright:    copyrightLine(),
				Viper:        viper.GetBool("useViper"),
				AppName:      path.Base(pkgName),
			}

			if err := project.Create(); err != nil {
				er(err)
			}

			fmt.Printf("Your Cobra applicaton is ready at\n%s\n", project.AbsolutePath)
		},
	}
)

func init() {
	initCmd.Flags().StringVar(&pkgName, "pkg-name", "", "fully qualified pkg name")
	initCmd.MarkFlagRequired("pkg-name")
}
<commit_msg>Correct all complaints from goimports<commit_after>// Copyright © 2015 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.

package cmd

import (
	"fmt"
	"os"
	"path"

	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

var (
	pkgName string

	initCmd = &cobra.Command{
		Use:     "init [name]",
		Aliases: []string{"initialize", "initialise", "create"},
		Short:   "Initialize a Cobra Application",
		Long: `Initialize (cobra init) will create a new application, with a license
and the appropriate structure for a Cobra-based CLI application.

  * If a name is provided, it will be created in the current directory;
  * If no name is provided, the current directory will be assumed;
  * If a relative path is provided, it will be created inside $GOPATH
    (e.g. github.com/spf13/hugo);
  * If an absolute path is provided, it will be created;
  * If the directory already exists but is empty, it will be used.

Init will not use an existing directory with contents.`,

		Run: func(cmd *cobra.Command, args []string) {

			wd, err := os.Getwd()
			if err != nil {
				er(err)
			}

			if len(args) > 0 {
				if args[0] != "." {
					wd = fmt.Sprintf("%s/%s", wd, args[0])
				}
			}

			project := &Project{
				AbsolutePath: wd,
				PkgName:      pkgName,
				Legal:        getLicense(),
				Copyright:    copyrightLine(),
				Viper:        viper.GetBool("useViper"),
				AppName:      path.Base(pkgName),
			}

			if err := project.Create(); err != nil {
				er(err)
			}

			fmt.Printf("Your Cobra applicaton is ready at\n%s\n", project.AbsolutePath)
		},
	}
)

func init() {
	initCmd.Flags().StringVar(&pkgName, "pkg-name", "", "fully qualified pkg name")
	initCmd.MarkFlagRequired("pkg-name")
}
<|endoftext|>


<commit_before>// Copyright 2015 RedHat, Inc.
// Copyright 2015 CoreOS, 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 sdjournal

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"strings"
	"testing"
	"time"

	"github.com/coreos/go-systemd/journal"
)

func TestJournalFollow(t *testing.T) {
	r, err := NewJournalReader(JournalReaderConfig{
		Since: time.Duration(-15) * time.Second,
		Matches: []Match{
			{
				Field: SD_JOURNAL_FIELD_SYSTEMD_UNIT,
				Value: "NetworkManager.service",
			},
		},
	})

	if err != nil {
		t.Fatalf("Error opening journal: %s", err)
	}

	if r == nil {
		t.Fatal("Got a nil reader")
	}

	defer r.Close()

	// start writing some test entries
	done := make(chan struct{}, 1)
	defer close(done)
	go func() {
		for {
			select {
			case <-done:
				return
			default:
				if err = journal.Print(journal.PriInfo, "test message %s", time.Now()); err != nil {
					t.Fatalf("Error writing to journal: %s", err)
				}

				time.Sleep(time.Second)
			}
		}
	}()

	// and follow the reader synchronously
	timeout := time.Duration(5) * time.Second
	if err = r.Follow(time.After(timeout), os.Stdout); err != ErrExpired {
		t.Fatalf("Error during follow: %s", err)
	}
}

func TestJournalGetUsage(t *testing.T) {
	j, err := NewJournal()

	if err != nil {
		t.Fatalf("Error opening journal: %s", err)
	}

	if j == nil {
		t.Fatal("Got a nil journal")
	}

	defer j.Close()

	_, err = j.GetUsage()

	if err != nil {
		t.Fatalf("Error getting journal size: %s", err)
	}
}

func TestJournalCursorGetSeekAndTest(t *testing.T) {
	j, err := NewJournal()
	if err != nil {
		t.Fatalf("Error opening journal: %s", err)
	}

	if j == nil {
		t.Fatal("Got a nil journal")
	}

	defer j.Close()

	waitAndNext := func(j *Journal) error {
		r := j.Wait(time.Duration(1) * time.Second)
		if r < 0 {
			return errors.New("Error waiting to journal")
		}

		n, err := j.Next()
		if err != nil {
			return fmt.Errorf("Error reading to journal: %s", err)
		}

		if n == 0 {
			return fmt.Errorf("Error reading to journal: %s", io.EOF)
		}

		return nil
	}

	err = journal.Print(journal.PriInfo, "test message for cursor %s", time.Now())
	if err != nil {
		t.Fatalf("Error writing to journal: %s", err)
	}

	if err = waitAndNext(j); err != nil {
		t.Fatalf(err.Error())
	}

	c, err := j.GetCursor()
	if err != nil {
		t.Fatalf("Error getting cursor from journal: %s", err)
	}

	err = j.SeekCursor(c)
	if err != nil {
		t.Fatalf("Error seeking cursor to journal: %s", err)
	}

	if err = waitAndNext(j); err != nil {
		t.Fatalf(err.Error())
	}

	err = j.TestCursor(c)
	if err != nil {
		t.Fatalf("Error testing cursor to journal: %s", err)
	}
}

func TestNewJournalFromDir(t *testing.T) {
	// test for error handling
	dir := "/ClearlyNonExistingPath/"
	j, err := NewJournalFromDir(dir)
	if err == nil {
		defer j.Close()
		t.Fatalf("Error expected when opening dummy path (%s)", dir)
	}
	// test for main code path
	dir, err = ioutil.TempDir("", "go-systemd-test")
	if err != nil {
		t.Fatalf("Error creating tempdir: %s", err)
	}
	defer os.RemoveAll(dir)
	j, err = NewJournalFromDir(dir)
	if err != nil {
		t.Fatalf("Error opening journal: %s", err)
	}
	if j == nil {
		t.Fatal("Got a nil journal")
	}
	j.Close()
}

func setupJournalRoundtrip() (*Journal, map[string]string, error) {
	j, err := NewJournal()
	if err != nil {
		return nil, nil, fmt.Errorf("Error opening journal: %s", err)
	}

	if j == nil {
		return nil, nil, fmt.Errorf("Got a nil journal")
	}

	j.FlushMatches()

	matchField := "TESTJOURNALENTRY"
	matchValue := fmt.Sprintf("%d", time.Now().UnixNano())
	m := Match{Field: matchField, Value: matchValue}
	err = j.AddMatch(m.String())
	if err != nil {
		return nil, nil, fmt.Errorf("Error adding matches to journal: %s", err)
	}

	msg := fmt.Sprintf("test journal get entry message %s", time.Now())
	data := map[string]string{matchField: matchValue}
	err = journal.Send(msg, journal.PriInfo, data)
	if err != nil {
		return nil, nil, fmt.Errorf("Error writing to journal: %s", err)
	}

	time.Sleep(time.Duration(1) * time.Second)

	n, err := j.Next()
	if err != nil {
		return nil, nil, fmt.Errorf("Error reading from journal: %s", err)
	}

	if n == 0 {
		return nil, nil, fmt.Errorf("Error reading from journal: %s", io.EOF)
	}

	data["MESSAGE"] = msg

	return j, data, nil
}

func TestJournalGetData(t *testing.T) {
	j, wantEntry, err := setupJournalRoundtrip()
	if err != nil {
		t.Fatal(err.Error())
	}

	defer j.Close()

	for k, v := range wantEntry {
		data := fmt.Sprintf("%s=%s", k, v)

		dataStr, err := j.GetData(k)
		if err != nil {
			t.Fatalf("GetData() error: %v", err)
		}

		if dataStr != data {
			t.Fatalf("Invalid data for \"%s\": got %s, want %s", k, dataStr, data)
		}

		dataBytes, err := j.GetDataBytes(k)
		if err != nil {
			t.Fatalf("GetDataBytes() error: %v", err)
		}

		if string(dataBytes) != data {
			t.Fatalf("Invalid data bytes for \"%s\": got %s, want %s", k, string(dataBytes), data)
		}

		valStr, err := j.GetDataValue(k)
		if err != nil {
			t.Fatalf("GetDataValue() error: %v", err)
		}

		if valStr != v {
			t.Fatalf("Invalid data value for \"%s\": got %s, want %s", k, valStr, v)
		}

		valBytes, err := j.GetDataValueBytes(k)
		if err != nil {
			t.Fatalf("GetDataValueBytes() error: %v", err)
		}

		if string(valBytes) != v {
			t.Fatalf("Invalid data value bytes for \"%s\": got %s, want %s", k, string(valBytes), v)
		}
	}
}

func TestJournalGetEntry(t *testing.T) {
	j, wantEntry, err := setupJournalRoundtrip()
	if err != nil {
		t.Fatal(err.Error())
	}

	defer j.Close()

	entry, err := j.GetEntry()
	if err != nil {
		t.Fatalf("Error getting the entry to journal: %s", err)
	}

	for k, wantV := range wantEntry {
		gotV := entry.Fields[k]
		if gotV != wantV {
			t.Fatalf("Bad result for entry.Fields[\"%s\"]: got %s, want %s", k, gotV, wantV)
		}
	}
}

// Check for incorrect read into small buffers,
// see https://github.com/coreos/go-systemd/issues/172
func TestJournalReaderSmallReadBuffer(t *testing.T) {
	// Write a long entry ...
	delim := "%%%%%%"
	longEntry := strings.Repeat("a", 256)
	matchField := "TESTJOURNALREADERSMALLBUF"
	matchValue := fmt.Sprintf("%d", time.Now().UnixNano())
	r, err := NewJournalReader(JournalReaderConfig{
		Since: time.Duration(-15) * time.Second,
		Matches: []Match{
			{
				Field: matchField,
				Value: matchValue,
			},
		},
	})
	if err != nil {
		t.Fatalf("Error opening journal: %s", err)
	}
	if r == nil {
		t.Fatal("Got a nil reader")
	}
	defer r.Close()

	want := fmt.Sprintf("%slongentry %s%s", delim, longEntry, delim)
	err = journal.Send(want, journal.PriInfo, map[string]string{matchField: matchValue})
	if err != nil {
		t.Fatal("Error writing to journal", err)
	}
	time.Sleep(time.Second)

	// ... and try to read it back piece by piece via a small buffer
	finalBuff := new(bytes.Buffer)
	var e error
	for c := -1; c != 0 && e == nil; {
		smallBuf := make([]byte, 5)
		c, e = r.Read(smallBuf)
		if c > len(smallBuf) {
			t.Fatalf("Got unexpected read length: %d vs %d", c, len(smallBuf))
		}
		_, _ = finalBuff.Write(smallBuf)
	}
	b := finalBuff.String()
	got := strings.Split(b, delim)
	if len(got) != 3 {
		t.Fatalf("Got unexpected entry %s", b)
	}
	if got[1] != strings.Trim(want, delim) {
		t.Fatalf("Got unexpected message %s", got[1])
	}
}
<commit_msg>sdjournal: fix race condition in test<commit_after>// Copyright 2015 RedHat, Inc.
// Copyright 2015 CoreOS, 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 sdjournal

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"strings"
	"testing"
	"time"

	"github.com/coreos/go-systemd/journal"
)

func TestJournalFollow(t *testing.T) {
	r, err := NewJournalReader(JournalReaderConfig{
		Since: time.Duration(-15) * time.Second,
		Matches: []Match{
			{
				Field: SD_JOURNAL_FIELD_SYSTEMD_UNIT,
				Value: "NetworkManager.service",
			},
		},
	})

	if err != nil {
		t.Fatalf("Error opening journal: %s", err)
	}

	if r == nil {
		t.Fatal("Got a nil reader")
	}

	defer r.Close()

	// start writing some test entries
	done := make(chan struct{}, 1)
	defer close(done)
	go func() {
		for {
			select {
			case <-done:
				return
			default:
				if err := journal.Print(journal.PriInfo, "test message %s", time.Now()); err != nil {
					t.Fatalf("Error writing to journal: %s", err)
				}

				time.Sleep(time.Second)
			}
		}
	}()

	// and follow the reader synchronously
	timeout := time.Duration(5) * time.Second
	if err = r.Follow(time.After(timeout), os.Stdout); err != ErrExpired {
		t.Fatalf("Error during follow: %s", err)
	}
}

func TestJournalGetUsage(t *testing.T) {
	j, err := NewJournal()

	if err != nil {
		t.Fatalf("Error opening journal: %s", err)
	}

	if j == nil {
		t.Fatal("Got a nil journal")
	}

	defer j.Close()

	_, err = j.GetUsage()

	if err != nil {
		t.Fatalf("Error getting journal size: %s", err)
	}
}

func TestJournalCursorGetSeekAndTest(t *testing.T) {
	j, err := NewJournal()
	if err != nil {
		t.Fatalf("Error opening journal: %s", err)
	}

	if j == nil {
		t.Fatal("Got a nil journal")
	}

	defer j.Close()

	waitAndNext := func(j *Journal) error {
		r := j.Wait(time.Duration(1) * time.Second)
		if r < 0 {
			return errors.New("Error waiting to journal")
		}

		n, err := j.Next()
		if err != nil {
			return fmt.Errorf("Error reading to journal: %s", err)
		}

		if n == 0 {
			return fmt.Errorf("Error reading to journal: %s", io.EOF)
		}

		return nil
	}

	err = journal.Print(journal.PriInfo, "test message for cursor %s", time.Now())
	if err != nil {
		t.Fatalf("Error writing to journal: %s", err)
	}

	if err = waitAndNext(j); err != nil {
		t.Fatalf(err.Error())
	}

	c, err := j.GetCursor()
	if err != nil {
		t.Fatalf("Error getting cursor from journal: %s", err)
	}

	err = j.SeekCursor(c)
	if err != nil {
		t.Fatalf("Error seeking cursor to journal: %s", err)
	}

	if err = waitAndNext(j); err != nil {
		t.Fatalf(err.Error())
	}

	err = j.TestCursor(c)
	if err != nil {
		t.Fatalf("Error testing cursor to journal: %s", err)
	}
}

func TestNewJournalFromDir(t *testing.T) {
	// test for error handling
	dir := "/ClearlyNonExistingPath/"
	j, err := NewJournalFromDir(dir)
	if err == nil {
		defer j.Close()
		t.Fatalf("Error expected when opening dummy path (%s)", dir)
	}
	// test for main code path
	dir, err = ioutil.TempDir("", "go-systemd-test")
	if err != nil {
		t.Fatalf("Error creating tempdir: %s", err)
	}
	defer os.RemoveAll(dir)
	j, err = NewJournalFromDir(dir)
	if err != nil {
		t.Fatalf("Error opening journal: %s", err)
	}
	if j == nil {
		t.Fatal("Got a nil journal")
	}
	j.Close()
}

func setupJournalRoundtrip() (*Journal, map[string]string, error) {
	j, err := NewJournal()
	if err != nil {
		return nil, nil, fmt.Errorf("Error opening journal: %s", err)
	}

	if j == nil {
		return nil, nil, fmt.Errorf("Got a nil journal")
	}

	j.FlushMatches()

	matchField := "TESTJOURNALENTRY"
	matchValue := fmt.Sprintf("%d", time.Now().UnixNano())
	m := Match{Field: matchField, Value: matchValue}
	err = j.AddMatch(m.String())
	if err != nil {
		return nil, nil, fmt.Errorf("Error adding matches to journal: %s", err)
	}

	msg := fmt.Sprintf("test journal get entry message %s", time.Now())
	data := map[string]string{matchField: matchValue}
	err = journal.Send(msg, journal.PriInfo, data)
	if err != nil {
		return nil, nil, fmt.Errorf("Error writing to journal: %s", err)
	}

	time.Sleep(time.Duration(1) * time.Second)

	n, err := j.Next()
	if err != nil {
		return nil, nil, fmt.Errorf("Error reading from journal: %s", err)
	}

	if n == 0 {
		return nil, nil, fmt.Errorf("Error reading from journal: %s", io.EOF)
	}

	data["MESSAGE"] = msg

	return j, data, nil
}

func TestJournalGetData(t *testing.T) {
	j, wantEntry, err := setupJournalRoundtrip()
	if err != nil {
		t.Fatal(err.Error())
	}

	defer j.Close()

	for k, v := range wantEntry {
		data := fmt.Sprintf("%s=%s", k, v)

		dataStr, err := j.GetData(k)
		if err != nil {
			t.Fatalf("GetData() error: %v", err)
		}

		if dataStr != data {
			t.Fatalf("Invalid data for \"%s\": got %s, want %s", k, dataStr, data)
		}

		dataBytes, err := j.GetDataBytes(k)
		if err != nil {
			t.Fatalf("GetDataBytes() error: %v", err)
		}

		if string(dataBytes) != data {
			t.Fatalf("Invalid data bytes for \"%s\": got %s, want %s", k, string(dataBytes), data)
		}

		valStr, err := j.GetDataValue(k)
		if err != nil {
			t.Fatalf("GetDataValue() error: %v", err)
		}

		if valStr != v {
			t.Fatalf("Invalid data value for \"%s\": got %s, want %s", k, valStr, v)
		}

		valBytes, err := j.GetDataValueBytes(k)
		if err != nil {
			t.Fatalf("GetDataValueBytes() error: %v", err)
		}

		if string(valBytes) != v {
			t.Fatalf("Invalid data value bytes for \"%s\": got %s, want %s", k, string(valBytes), v)
		}
	}
}

func TestJournalGetEntry(t *testing.T) {
	j, wantEntry, err := setupJournalRoundtrip()
	if err != nil {
		t.Fatal(err.Error())
	}

	defer j.Close()

	entry, err := j.GetEntry()
	if err != nil {
		t.Fatalf("Error getting the entry to journal: %s", err)
	}

	for k, wantV := range wantEntry {
		gotV := entry.Fields[k]
		if gotV != wantV {
			t.Fatalf("Bad result for entry.Fields[\"%s\"]: got %s, want %s", k, gotV, wantV)
		}
	}
}

// Check for incorrect read into small buffers,
// see https://github.com/coreos/go-systemd/issues/172
func TestJournalReaderSmallReadBuffer(t *testing.T) {
	// Write a long entry ...
	delim := "%%%%%%"
	longEntry := strings.Repeat("a", 256)
	matchField := "TESTJOURNALREADERSMALLBUF"
	matchValue := fmt.Sprintf("%d", time.Now().UnixNano())
	r, err := NewJournalReader(JournalReaderConfig{
		Since: time.Duration(-15) * time.Second,
		Matches: []Match{
			{
				Field: matchField,
				Value: matchValue,
			},
		},
	})
	if err != nil {
		t.Fatalf("Error opening journal: %s", err)
	}
	if r == nil {
		t.Fatal("Got a nil reader")
	}
	defer r.Close()

	want := fmt.Sprintf("%slongentry %s%s", delim, longEntry, delim)
	err = journal.Send(want, journal.PriInfo, map[string]string{matchField: matchValue})
	if err != nil {
		t.Fatal("Error writing to journal", err)
	}
	time.Sleep(time.Second)

	// ... and try to read it back piece by piece via a small buffer
	finalBuff := new(bytes.Buffer)
	var e error
	for c := -1; c != 0 && e == nil; {
		smallBuf := make([]byte, 5)
		c, e = r.Read(smallBuf)
		if c > len(smallBuf) {
			t.Fatalf("Got unexpected read length: %d vs %d", c, len(smallBuf))
		}
		_, _ = finalBuff.Write(smallBuf)
	}
	b := finalBuff.String()
	got := strings.Split(b, delim)
	if len(got) != 3 {
		t.Fatalf("Got unexpected entry %s", b)
	}
	if got[1] != strings.Trim(want, delim) {
		t.Fatalf("Got unexpected message %s", got[1])
	}
}
<|endoftext|>


<commit_before>package forestdb

//  Copyright (c) 2014 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.

//#cgo LDFLAGS: -lforestdb
//#cgo CFLAGS: -O0
//#include <stdlib.h>
//#include <libforestdb/forestdb.h>
import "C"

// KVStore handle
type KVStore struct {
	advLock
	f  *File
	db *C.fdb_kvs_handle
}

// Close the KVStore and release related resources.
func (k *KVStore) Close() error {
	k.Lock()
	defer k.Unlock()

	Log.Tracef("fdb_kvs_close call k:%p db:%v", k, k.db)
	errNo := C.fdb_kvs_close(k.db)
	Log.Tracef("fdb_kvs_close retn k:%p errNo:%v", k, errNo)
	if errNo != RESULT_SUCCESS {
		return Error(errNo)
	}
	return nil
}

// Info returns the information about a given kvstore
func (k *KVStore) Info() (*KVStoreInfo, error) {
	k.Lock()
	defer k.Unlock()

	rv := KVStoreInfo{}
	Log.Tracef("fdb_get_kvs_info call k:%p db:%v", k, k.db)
	errNo := C.fdb_get_kvs_info(k.db, &rv.info)
	Log.Tracef("fdb_kvs_close retn k:%p errNo:%v info:%v", k, errNo, rv.info)
	if errNo != RESULT_SUCCESS {
		return nil, Error(errNo)
	}
	return &rv, nil
}

// Get retrieves the metadata and doc body for a given key
func (k *KVStore) Get(doc *Doc) error {
	k.Lock()
	defer k.Unlock()

	Log.Tracef("fdb_get call k:%p db:%v doc:%v", k, k.db, doc.doc)
	errNo := C.fdb_get(k.db, doc.doc)
	Log.Tracef("fdb_get retn k:%p errNo:%v doc:%v", k, errNo, doc.doc)
	if errNo != RESULT_SUCCESS {
		return Error(errNo)
	}
	return nil
}

// GetMetaOnly retrieves the metadata for a given key
func (k *KVStore) GetMetaOnly(doc *Doc) error {
	k.Lock()
	defer k.Unlock()

	Log.Tracef("fdb_get_metaonly call k:%p db:%v doc:%v", k, k.db, doc.doc)
	errNo := C.fdb_get_metaonly(k.db, doc.doc)
	Log.Tracef("fdb_get_metaonly retn k:%p errNo:%v doc:%v", k, errNo, doc.doc)
	if errNo != RESULT_SUCCESS {
		return Error(errNo)
	}
	return nil
}

// GetBySeq retrieves the metadata and doc body for a given sequence number
func (k *KVStore) GetBySeq(doc *Doc) error {
	k.Lock()
	defer k.Unlock()

	Log.Tracef("fdb_get_byseq call k:%p db:%v doc:%v", k, k.db, doc.doc)
	errNo := C.fdb_get_byseq(k.db, doc.doc)
	Log.Tracef("fdb_get_byseq retn k:%p errNo:%v doc:%v", k, errNo, doc.doc)
	if errNo != RESULT_SUCCESS {
		return Error(errNo)
	}
	return nil
}

// GetMetaOnlyBySeq retrieves the metadata for a given sequence number
func (k *KVStore) GetMetaOnlyBySeq(doc *Doc) error {
	k.Lock()
	defer k.Unlock()

	Log.Tracef("fdb_get_metaonly_byseq call k:%p db:%v doc:%v", k, k.db, doc.doc)
	errNo := C.fdb_get_metaonly_byseq(k.db, doc.doc)
	Log.Tracef("fdb_get_metaonly_byseq retn k:%p errNo:%v doc:%v", k, errNo, doc.doc)
	if errNo != RESULT_SUCCESS {
		return Error(errNo)
	}
	return nil
}

// GetByOffset retrieves a doc's metadata and body with a given doc offset in the database file
func (k *KVStore) GetByOffset(doc *Doc) error {
	k.Lock()
	defer k.Unlock()

	Log.Tracef("fdb_get_byoffset call k:%p db:%v doc:%v", k, k.db, doc.doc)
	errNo := C.fdb_get_byoffset(k.db, doc.doc)
	Log.Tracef("fdb_get_byoffset retn k:%p errNo:%v doc:%v", k, errNo, doc.doc)
	if errNo != RESULT_SUCCESS {
		return Error(errNo)
	}
	return nil
}

// Set update the metadata and doc body for a given key
func (k *KVStore) Set(doc *Doc) error {
	k.Lock()
	defer k.Unlock()

	Log.Tracef("fdb_set call k:%p db:%v doc:%v", k, k.db, doc.doc)
	errNo := C.fdb_set(k.db, doc.doc)
	Log.Tracef("fdb_set retn k:%p errNo:%v doc:%v", k, errNo, doc.doc)
	if errNo != RESULT_SUCCESS {
		return Error(errNo)
	}
	return nil
}

// Delete deletes a key, its metadata and value
func (k *KVStore) Delete(doc *Doc) error {
	k.Lock()
	defer k.Unlock()

	Log.Tracef("fdb_del call k:%p db:%v doc:%v", k, k.db, doc.doc)
	errNo := C.fdb_del(k.db, doc.doc)
	Log.Tracef("fdb_set retn k:%p errNo:%v doc:%v", k, errNo, doc.doc)
	if errNo != RESULT_SUCCESS {
		return Error(errNo)
	}
	return nil
}

// Shutdown destroys all the resources (e.g., buffer cache, in-memory WAL indexes, daemon compaction thread, etc.) and then shutdown the ForestDB engine
func Shutdown() error {
	Log.Tracef("fdb_shutdown call")
	errNo := C.fdb_shutdown()
	Log.Tracef("fdb_shutdown retn errNo:%v", errNo)
	if errNo != RESULT_SUCCESS {
		return Error(errNo)
	}
	return nil
}
<commit_msg>MB-14592 fdb: Add forestdb.BufferCacheUsed() API<commit_after>package forestdb

//  Copyright (c) 2014 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.

//#cgo LDFLAGS: -lforestdb
//#cgo CFLAGS: -O0
//#include <stdlib.h>
//#include <libforestdb/forestdb.h>
import "C"

// KVStore handle
type KVStore struct {
	advLock
	f  *File
	db *C.fdb_kvs_handle
}

// Close the KVStore and release related resources.
func (k *KVStore) Close() error {
	k.Lock()
	defer k.Unlock()

	Log.Tracef("fdb_kvs_close call k:%p db:%v", k, k.db)
	errNo := C.fdb_kvs_close(k.db)
	Log.Tracef("fdb_kvs_close retn k:%p errNo:%v", k, errNo)
	if errNo != RESULT_SUCCESS {
		return Error(errNo)
	}
	return nil
}

// Info returns the information about a given kvstore
func (k *KVStore) Info() (*KVStoreInfo, error) {
	k.Lock()
	defer k.Unlock()

	rv := KVStoreInfo{}
	Log.Tracef("fdb_get_kvs_info call k:%p db:%v", k, k.db)
	errNo := C.fdb_get_kvs_info(k.db, &rv.info)
	Log.Tracef("fdb_kvs_close retn k:%p errNo:%v info:%v", k, errNo, rv.info)
	if errNo != RESULT_SUCCESS {
		return nil, Error(errNo)
	}
	return &rv, nil
}

// Get retrieves the metadata and doc body for a given key
func (k *KVStore) Get(doc *Doc) error {
	k.Lock()
	defer k.Unlock()

	Log.Tracef("fdb_get call k:%p db:%v doc:%v", k, k.db, doc.doc)
	errNo := C.fdb_get(k.db, doc.doc)
	Log.Tracef("fdb_get retn k:%p errNo:%v doc:%v", k, errNo, doc.doc)
	if errNo != RESULT_SUCCESS {
		return Error(errNo)
	}
	return nil
}

// GetMetaOnly retrieves the metadata for a given key
func (k *KVStore) GetMetaOnly(doc *Doc) error {
	k.Lock()
	defer k.Unlock()

	Log.Tracef("fdb_get_metaonly call k:%p db:%v doc:%v", k, k.db, doc.doc)
	errNo := C.fdb_get_metaonly(k.db, doc.doc)
	Log.Tracef("fdb_get_metaonly retn k:%p errNo:%v doc:%v", k, errNo, doc.doc)
	if errNo != RESULT_SUCCESS {
		return Error(errNo)
	}
	return nil
}

// GetBySeq retrieves the metadata and doc body for a given sequence number
func (k *KVStore) GetBySeq(doc *Doc) error {
	k.Lock()
	defer k.Unlock()

	Log.Tracef("fdb_get_byseq call k:%p db:%v doc:%v", k, k.db, doc.doc)
	errNo := C.fdb_get_byseq(k.db, doc.doc)
	Log.Tracef("fdb_get_byseq retn k:%p errNo:%v doc:%v", k, errNo, doc.doc)
	if errNo != RESULT_SUCCESS {
		return Error(errNo)
	}
	return nil
}

// GetMetaOnlyBySeq retrieves the metadata for a given sequence number
func (k *KVStore) GetMetaOnlyBySeq(doc *Doc) error {
	k.Lock()
	defer k.Unlock()

	Log.Tracef("fdb_get_metaonly_byseq call k:%p db:%v doc:%v", k, k.db, doc.doc)
	errNo := C.fdb_get_metaonly_byseq(k.db, doc.doc)
	Log.Tracef("fdb_get_metaonly_byseq retn k:%p errNo:%v doc:%v", k, errNo, doc.doc)
	if errNo != RESULT_SUCCESS {
		return Error(errNo)
	}
	return nil
}

// GetByOffset retrieves a doc's metadata and body with a given doc offset in the database file
func (k *KVStore) GetByOffset(doc *Doc) error {
	k.Lock()
	defer k.Unlock()

	Log.Tracef("fdb_get_byoffset call k:%p db:%v doc:%v", k, k.db, doc.doc)
	errNo := C.fdb_get_byoffset(k.db, doc.doc)
	Log.Tracef("fdb_get_byoffset retn k:%p errNo:%v doc:%v", k, errNo, doc.doc)
	if errNo != RESULT_SUCCESS {
		return Error(errNo)
	}
	return nil
}

// Set update the metadata and doc body for a given key
func (k *KVStore) Set(doc *Doc) error {
	k.Lock()
	defer k.Unlock()

	Log.Tracef("fdb_set call k:%p db:%v doc:%v", k, k.db, doc.doc)
	errNo := C.fdb_set(k.db, doc.doc)
	Log.Tracef("fdb_set retn k:%p errNo:%v doc:%v", k, errNo, doc.doc)
	if errNo != RESULT_SUCCESS {
		return Error(errNo)
	}
	return nil
}

// Delete deletes a key, its metadata and value
func (k *KVStore) Delete(doc *Doc) error {
	k.Lock()
	defer k.Unlock()

	Log.Tracef("fdb_del call k:%p db:%v doc:%v", k, k.db, doc.doc)
	errNo := C.fdb_del(k.db, doc.doc)
	Log.Tracef("fdb_set retn k:%p errNo:%v doc:%v", k, errNo, doc.doc)
	if errNo != RESULT_SUCCESS {
		return Error(errNo)
	}
	return nil
}

// Shutdown destroys all the resources (e.g., buffer cache, in-memory WAL indexes, daemon compaction thread, etc.) and then shutdown the ForestDB engine
func Shutdown() error {
	Log.Tracef("fdb_shutdown call")
	errNo := C.fdb_shutdown()
	Log.Tracef("fdb_shutdown retn errNo:%v", errNo)
	if errNo != RESULT_SUCCESS {
		return Error(errNo)
	}
	return nil
}

// Buffer cache used by the forestdb global pool
func BufferCacheUsed() uint64 {
	return uint64(C.fdb_get_buffer_cache_used())
}
<|endoftext|>


<commit_before>// Copyright © 2015 - Rémy MATHIEU

package api

import (
	"encoding/json"
	"io/ioutil"
	"log"
	"net/http"
	"time"

	. "github.com/remeh/home-sensors-server/app"
	"github.com/remeh/home-sensors-server/service"

	"github.com/gorilla/mux"
)

type SensorHit struct {
	App *App
}

type sensorHitBody struct {
	Values []struct {
		Type  string  `json:"type"`
		Value float64 `json:"value"`
	} `json:"values"`
}

func (c SensorHit) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	r.ParseForm()

	sensorId := vars["sensor"]

	if len(sensorId) == 0 {
		w.WriteHeader(400)
		return
	}

	// read the body

	data, err := ioutil.ReadAll(r.Body)
	if err != nil {
		w.WriteHeader(500)
		log.Println("err:", err)
		return
	}

	// unmarshal the body

	var body sensorHitBody
	err = json.Unmarshal(data, &body)
	if err != nil {
		w.WriteHeader(500)
		log.Println("err:", err)
		return
	}

	// store the values

	for _, v := range body.Values {
		err = service.StoreSensorValue(c.App, sensorId, v.Type, time.Now(), v.Value)
		if err != nil {
			w.WriteHeader(500)
			log.Println("err: ", err)
			return
		}
	}
}
<commit_msg>api/hit: the value is a string which need to be parsed.<commit_after>// Copyright © 2015 - Rémy MATHIEU

package api

import (
	"encoding/json"
	"io/ioutil"
	"log"
	"net/http"
	"time"

	. "github.com/remeh/home-sensors-server/app"
	"github.com/remeh/home-sensors-server/service"

	"github.com/gorilla/mux"
)

type SensorHit struct {
	App *App
}

type sensorHitBody struct {
	Values []struct {
		Type  string `json:"type"`
		Value string `json:"value"`
	} `json:"values"`
}

func (c SensorHit) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	r.ParseForm()

	sensorId := vars["sensor"]

	if len(sensorId) == 0 {
		w.WriteHeader(400)
		return
	}

	// read the body

	data, err := ioutil.ReadAll(r.Body)
	if err != nil {
		w.WriteHeader(500)
		log.Println("err:", err)
		return
	}

	// unmarshal the body

	var body sensorHitBody
	err = json.Unmarshal(data, &body)
	if err != nil {
		w.WriteHeader(500)
		log.Println("err:", err)
		return
	}

	// store the values

	f, err := strconv.ParseFloat(v.Value, 64)
	if err != nil {
		w.WriteHeader(500)
		log.Println("err:", err)
		return
	}

	for _, v := range body.Values {
		err = service.StoreSensorValue(c.App, sensorId, v.Type, time.Now(), f)
		if err != nil {
			w.WriteHeader(500)
			log.Println("err: ", err)
			return
		}
	}
}
<|endoftext|>


<commit_before>package worker

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/koding/logging"
	"labix.org/v2/mgo"

	"github.com/jinzhu/gorm"
	"github.com/koding/rabbitmq"
	"github.com/streadway/amqp"
)

type Listener struct {
	Consumer             *rabbitmq.Consumer
	MaintenancePublisher *rabbitmq.Producer
	SourceExchangeName   string
	WorkerName           string
	Log                  logging.Logger
}

func NewListener(workerName string, sourceExchangeName string, log logging.Logger) *Listener {
	return &Listener{
		WorkerName:         workerName,
		SourceExchangeName: sourceExchangeName,
		Log:                log,
	}
}

func (l *Listener) createConsumer(rmq *rabbitmq.RabbitMQ) *rabbitmq.Consumer {
	exchange := rabbitmq.Exchange{
		Name:    l.SourceExchangeName,
		Type:    "fanout",
		Durable: true,
	}

	queue := rabbitmq.Queue{
		Name:    fmt.Sprintf("%s:WorkerQueue", l.WorkerName),
		Durable: true,
	}

	binding := rabbitmq.BindingOptions{
		RoutingKey: "",
	}

	consumerOptions := rabbitmq.ConsumerOptions{
		Tag: fmt.Sprintf("%sWorkerConsumer", l.WorkerName),
	}

	Consumer, err := rmq.NewConsumer(exchange, queue, binding, consumerOptions)
	if err != nil {
		panic(err)
	}

	return Consumer
}

func (l *Listener) createMaintenancePublisher(rmq *rabbitmq.RabbitMQ) *rabbitmq.Producer {
	exchange := rabbitmq.Exchange{
		Name: "",
	}

	publishingOptions := rabbitmq.PublishingOptions{
		Tag:       fmt.Sprintf("%sWorkerConsumer", l.WorkerName),
		Immediate: false,
		// RoutingKey: "ApiMaintenanceQueue", // publish to ApiMaintenanceQueue
	}

	producer, err := rmq.NewProducer(
		exchange,
		rabbitmq.Queue{
			Name:    "ApiMaintenanceQueue",
			Durable: true,
		},
		publishingOptions,
	)
	if err != nil {
		panic(err)
	}

	return producer
}

func (l *Listener) Listen(rmq *rabbitmq.RabbitMQ, handler Handler) {
	// set consumer
	l.Consumer = l.createConsumer(rmq)
	l.MaintenancePublisher = l.createMaintenancePublisher(rmq)

	if err := l.Consumer.QOS(10); err != nil {
		panic(err)
	}

	l.Consumer.Consume(l.Start(handler))
}

func (l *Listener) Close() {
	l.Consumer.Shutdown()
	l.MaintenancePublisher.Shutdown()
}

var HandlerNotFoundErr = errors.New("Handler Not Found")

type Handler interface {
	HandleEvent(string, []byte) error
	ErrHandler
}

type ErrHandler interface {
	// bool is whether publishing the message to maintenance qeueue or not
	DefaultErrHandler(amqp.Delivery, error) bool
}

func (l *Listener) Start(handler Handler) func(delivery amqp.Delivery) {
	l.Log.Info("Worker Started to Consume")
	return func(delivery amqp.Delivery) {
		err := handler.HandleEvent(delivery.Type, delivery.Body)
		switch err {
		case nil:
			delivery.Ack(false)
		case HandlerNotFoundErr:
			l.Log.Notice("unknown event type (%s) recieved, \n deleting message from RMQ", delivery.Type)
			delivery.Ack(false)
		case gorm.RecordNotFound:
			l.Log.Warning("Record not found in our db (%s) recieved, \n deleting message from RMQ", string(delivery.Body))
			delivery.Ack(false)
		case mgo.ErrNotFound:
			l.Log.Warning("Record not found in our mongo db (%s) recieved, deleting message from RMQ", string(delivery.Body))
			delivery.Ack(false)
		default:
			if handler.DefaultErrHandler(delivery, err) {
				data, err := json.Marshal(delivery)
				if err == nil {
					msg := amqp.Publishing{
						Body:  []byte(data),
						AppId: l.WorkerName,
					}
					l.MaintenancePublisher.Publish(msg)
				}
			}
		}
	}
}
<commit_msg>Social: remove new lines<commit_after>package worker

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/koding/logging"
	"labix.org/v2/mgo"

	"github.com/jinzhu/gorm"
	"github.com/koding/rabbitmq"
	"github.com/streadway/amqp"
)

type Listener struct {
	Consumer             *rabbitmq.Consumer
	MaintenancePublisher *rabbitmq.Producer
	SourceExchangeName   string
	WorkerName           string
	Log                  logging.Logger
}

func NewListener(workerName string, sourceExchangeName string, log logging.Logger) *Listener {
	return &Listener{
		WorkerName:         workerName,
		SourceExchangeName: sourceExchangeName,
		Log:                log,
	}
}

func (l *Listener) createConsumer(rmq *rabbitmq.RabbitMQ) *rabbitmq.Consumer {
	exchange := rabbitmq.Exchange{
		Name:    l.SourceExchangeName,
		Type:    "fanout",
		Durable: true,
	}

	queue := rabbitmq.Queue{
		Name:    fmt.Sprintf("%s:WorkerQueue", l.WorkerName),
		Durable: true,
	}

	binding := rabbitmq.BindingOptions{
		RoutingKey: "",
	}

	consumerOptions := rabbitmq.ConsumerOptions{
		Tag: fmt.Sprintf("%sWorkerConsumer", l.WorkerName),
	}

	Consumer, err := rmq.NewConsumer(exchange, queue, binding, consumerOptions)
	if err != nil {
		panic(err)
	}

	return Consumer
}

func (l *Listener) createMaintenancePublisher(rmq *rabbitmq.RabbitMQ) *rabbitmq.Producer {
	exchange := rabbitmq.Exchange{
		Name: "",
	}

	publishingOptions := rabbitmq.PublishingOptions{
		Tag:       fmt.Sprintf("%sWorkerConsumer", l.WorkerName),
		Immediate: false,
		// RoutingKey: "ApiMaintenanceQueue", // publish to ApiMaintenanceQueue
	}

	producer, err := rmq.NewProducer(
		exchange,
		rabbitmq.Queue{
			Name:    "ApiMaintenanceQueue",
			Durable: true,
		},
		publishingOptions,
	)
	if err != nil {
		panic(err)
	}

	return producer
}

func (l *Listener) Listen(rmq *rabbitmq.RabbitMQ, handler Handler) {
	// set consumer
	l.Consumer = l.createConsumer(rmq)
	l.MaintenancePublisher = l.createMaintenancePublisher(rmq)

	if err := l.Consumer.QOS(10); err != nil {
		panic(err)
	}

	l.Consumer.Consume(l.Start(handler))
}

func (l *Listener) Close() {
	l.Consumer.Shutdown()
	l.MaintenancePublisher.Shutdown()
}

var HandlerNotFoundErr = errors.New("Handler Not Found")

type Handler interface {
	HandleEvent(string, []byte) error
	ErrHandler
}

type ErrHandler interface {
	// bool is whether publishing the message to maintenance qeueue or not
	DefaultErrHandler(amqp.Delivery, error) bool
}

func (l *Listener) Start(handler Handler) func(delivery amqp.Delivery) {
	l.Log.Info("Worker Started to Consume")
	return func(delivery amqp.Delivery) {
		err := handler.HandleEvent(delivery.Type, delivery.Body)
		switch err {
		case nil:
			delivery.Ack(false)
		case HandlerNotFoundErr:
			l.Log.Notice("unknown event type (%s) recieved, deleting message from RMQ", delivery.Type)
			delivery.Ack(false)
		case gorm.RecordNotFound:
			l.Log.Warning("Record not found in our db (%s) recieved, deleting message from RMQ", string(delivery.Body))
			delivery.Ack(false)
		case mgo.ErrNotFound:
			l.Log.Warning("Record not found in our mongo db (%s) recieved, deleting message from RMQ", string(delivery.Body))
			delivery.Ack(false)
		default:
			if handler.DefaultErrHandler(delivery, err) {
				data, err := json.Marshal(delivery)
				if err == nil {
					msg := amqp.Publishing{
						Body:  []byte(data),
						AppId: l.WorkerName,
					}
					l.MaintenancePublisher.Publish(msg)
				}
			}
		}
	}
}
<|endoftext|>


<commit_before>package v1alpha1

import (
	"os"
	"strconv"
)

const (

	// EnvVarFakeInClusterConfig is an environment variable to fake an in-cluster RESTConfig using
	// the current kubectl context (for development purposes)
	EnvVarFakeInClusterConfig = "ARGOCD_FAKE_IN_CLUSTER"

	// EnvK8sClientQPS is the QPS value used for the kubernetes client (default: 50)
	EnvK8sClientQPS = "ARGOCD_K8S_CLIENT_QPS"

	// EnvK8sClientBurst is the burst value used for the kubernetes client (default: twice the client QPS)
	EnvK8sClientBurst = "ARGOCD_K8S_CLIENT_BURST"

	// EnvK8sClientMaxIdleConnections is the number of max idle connections in K8s REST client HTTP transport (default: 500)
	EnvK8sClientMaxIdleConnections = "ARGOCD_K8S_CLIENT_MAX_IDLE_CONNECTIONS"
)

// Constants associated with the Cluster API
var (

	// K8sClientConfigQPS controls the QPS to be used in K8s REST client configs
	K8sClientConfigQPS float32 = 50

	// K8sClientConfigBurst controls the burst to be used in K8s REST client configs
	K8sClientConfigBurst int = 100

	// K8sMaxIdleConnections controls the number of max idle connections in K8s REST client HTTP transport
	K8sMaxIdleConnections = 500
)

func init() {
	if envQPS := os.Getenv(EnvK8sClientQPS); envQPS != "" {
		if qps, err := strconv.ParseFloat(envQPS, 32); err != nil {
			K8sClientConfigQPS = float32(qps)
		}
	}
	if envBurst := os.Getenv(EnvK8sClientBurst); envBurst != "" {
		if burst, err := strconv.Atoi(envBurst); err != nil {
			K8sClientConfigBurst = burst
		}
	} else {
		K8sClientConfigBurst = 2 * int(K8sClientConfigQPS)
	}

	if envMaxConn := os.Getenv(EnvK8sClientMaxIdleConnections); envMaxConn != "" {
		if maxConn, err := strconv.Atoi(envMaxConn); err != nil {
			K8sMaxIdleConnections = maxConn
		}
	}

}
<commit_msg>fix: env vars to tune cluster cache were broken (#7779)<commit_after>package v1alpha1

import (
	"os"
	"strconv"
)

const (

	// EnvVarFakeInClusterConfig is an environment variable to fake an in-cluster RESTConfig using
	// the current kubectl context (for development purposes)
	EnvVarFakeInClusterConfig = "ARGOCD_FAKE_IN_CLUSTER"

	// EnvK8sClientQPS is the QPS value used for the kubernetes client (default: 50)
	EnvK8sClientQPS = "ARGOCD_K8S_CLIENT_QPS"

	// EnvK8sClientBurst is the burst value used for the kubernetes client (default: twice the client QPS)
	EnvK8sClientBurst = "ARGOCD_K8S_CLIENT_BURST"

	// EnvK8sClientMaxIdleConnections is the number of max idle connections in K8s REST client HTTP transport (default: 500)
	EnvK8sClientMaxIdleConnections = "ARGOCD_K8S_CLIENT_MAX_IDLE_CONNECTIONS"
)

// Constants associated with the Cluster API
var (

	// K8sClientConfigQPS controls the QPS to be used in K8s REST client configs
	K8sClientConfigQPS float32 = 50

	// K8sClientConfigBurst controls the burst to be used in K8s REST client configs
	K8sClientConfigBurst int = 100

	// K8sMaxIdleConnections controls the number of max idle connections in K8s REST client HTTP transport
	K8sMaxIdleConnections = 500
)

func init() {
	if envQPS := os.Getenv(EnvK8sClientQPS); envQPS != "" {
		if qps, err := strconv.ParseFloat(envQPS, 32); err == nil {
			K8sClientConfigQPS = float32(qps)
		}
	}
	if envBurst := os.Getenv(EnvK8sClientBurst); envBurst != "" {
		if burst, err := strconv.Atoi(envBurst); err == nil {
			K8sClientConfigBurst = burst
		}
	} else {
		K8sClientConfigBurst = 2 * int(K8sClientConfigQPS)
	}

	if envMaxConn := os.Getenv(EnvK8sClientMaxIdleConnections); envMaxConn != "" {
		if maxConn, err := strconv.Atoi(envMaxConn); err == nil {
			K8sMaxIdleConnections = maxConn
		}
	}

}
<|endoftext|>


<commit_before>package models

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	c.DeletedAt = ZeroDate()
}

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

	c.DeletedAt = ZeroDate()
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	if account.IsTroll {
		return true, nil
	}

	return false, nil
}

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

	return nil
}

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

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

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

	return bongo.B.Create(c)
}

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

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

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

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

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

//  FetchByIds fetchs given ids from database, it doesnt add any meta bits
// properties into query
func (c *ChannelMessage) FetchByIds(ids []int64) ([]ChannelMessage, error) {
	var messages []ChannelMessage

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

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

	return messages, nil
}

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

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

	return containers, nil
}

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

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

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

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

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

	cmc.Replies = populatedChannelMessagesReplies
	return cmc, nil
}

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

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

		return false, err
	}

	return true, nil
}

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

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

	container.AccountOldId = oldId

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

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

	return container, nil
}

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

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

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

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

	interactionContainer := NewInteractionContainer()
	interactionContainer.ActorsPreview = oldIds

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

	interactionContainer.IsInteracted = isInteracted

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

	interactionContainer.ActorsCount = count

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

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

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

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

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

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

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

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

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

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

	return flattened
}

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

	return c.CountWithQuery(query)
}

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

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

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

	return messageIds, nil
}

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

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

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

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

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

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

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

	return nil
}
<commit_msg>Social: send `query` into messageReply.count method<commit_after>package models

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	c.DeletedAt = ZeroDate()
}

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

	c.DeletedAt = ZeroDate()
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	if account.IsTroll {
		return true, nil
	}

	return false, nil
}

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

	return nil
}

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

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

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

	return bongo.B.Create(c)
}

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

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

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

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

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

//  FetchByIds fetchs given ids from database, it doesnt add any meta bits
// properties into query
func (c *ChannelMessage) FetchByIds(ids []int64) ([]ChannelMessage, error) {
	var messages []ChannelMessage

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

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

	return messages, nil
}

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

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

	return containers, nil
}

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

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

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

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

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

	cmc.Replies = populatedChannelMessagesReplies
	return cmc, nil
}

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

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

		return false, err
	}

	return true, nil
}

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

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

	container.AccountOldId = oldId

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

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

	return container, nil
}

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

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

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

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

	interactionContainer := NewInteractionContainer()
	interactionContainer.ActorsPreview = oldIds

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

	interactionContainer.IsInteracted = isInteracted

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

	interactionContainer.ActorsCount = count

	container.Interactions[query.Type] = interactionContainer
	return container, nil
}

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

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

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

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

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

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

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

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

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

	return flattened
}

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

	return c.CountWithQuery(query)
}

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

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

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

	return messageIds, nil
}

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

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

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

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

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

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

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

	return nil
}
<|endoftext|>