<commit_before>// @author Robin Verlangen
// API handler

package main

// Imports
import (
	"encoding/json"
	"fmt"
	"github.com/pmylund/go-cache"
	"log"
	"os"
	"os/exec"
	"strings"
	"time"
)

const DATASTORE_TASK_TTL = "86400" // 24 hours

// Task struct
type Task struct {
	Id               string   // Unique task id
	targets          []*Node  // List of target nodes
	Commands         []string // List of commands to execute in that order
	CreatedTimestamp int64    //  Created
}

// Local task (on a specific node)
type LocalTask struct {
	Id       string   // Unique task id
	Commands []string // List of commands to execute in that order
}

// Task discoverer
type TaskDiscoverer struct {
	executionQueue chan *LocalTask // Queue of tasks to be executed
	executionCache *cache.Cache    // Cache of executed task ids
}

// New task discoverer
func NewTaskDiscoverer() *TaskDiscoverer {
	return &TaskDiscoverer{
		executionQueue: make(chan *LocalTask, 1000),
		executionCache: cache.New(60*time.Minute, 30*time.Second),
	}
}

// Task completion key
func GetCompletionKey(taskId string) string {
	return fmt.Sprintf("task~%s~completed~%s", taskId, instanceId)
}

// Task completion key
func GetOutputKey(taskId string) string {
	return fmt.Sprintf("task~%s~output~%s", taskId, instanceId)
}

// Save output
func (lt *LocalTask) SaveOutput(output string) bool {
	return datastore.PutEntry(GetOutputKey(lt.Id), output, DATASTORE_TASK_TTL)
}

// Run local task
func (lt *LocalTask) Run() bool {
	// Task file
	var tmpFolder string = "/tmp" // @todo Configure
	var shell string = "sh"       // @todo Configure
	var tmpFile string = fmt.Sprintf("%s/%s", tmpFolder, lt.Id)
	file, fopenErr := os.OpenFile(tmpFile, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666)
	if fopenErr != nil {
		log.Println(fmt.Sprintf("ERROR: Failed to open tmp task file %s", fopenErr))
		return false
	}
	if debug {
		log.Println(fmt.Sprintf("DEBUG: Opened tmp task file in %s", file.Name()))
	}

	// Print commands to file
	//file.WriteString(fmt.Sprintf("#!/bin/%s\n", shell))
	for _, cmd := range lt.Commands {
		file.WriteString(cmd + "\n")
		if debug {
			log.Println(fmt.Sprintf("Writing to '%s': %s", file.Name(), cmd))
		}
	}
	file.Sync()
	file.Close()

	// Execute file
	output, execErr := exec.Command(shell, file.Name()).Output()
	datastore.PutEntry(GetCompletionKey(lt.Id), "1", DATASTORE_TASK_TTL)
	if execErr != nil {
		log.Println(fmt.Sprintf("ERR: Failed to execute tmp task file: %s", execErr))
		return false
	}
	lt.SaveOutput(string(output))
	if debug {
		log.Println(fmt.Sprintf("DEBUG: Task output: %s", output))
	}

	// Cleanup
	removeErr := os.Remove(file.Name())
	if removeErr != nil {
		log.Println(fmt.Sprintf("ERR: Failed to remove tmp task file: %s", removeErr))
		return false
	}
	if debug {
		log.Println(fmt.Sprintf("DEBUG: Removed tmp task file in %s", file.Name()))
	}

	return false
}

// Discover new tasks from queue
func (td *TaskDiscoverer) Discover() bool {
	// Datastore ready?
	if datastore == nil {
		if debug {
			log.Println("DEBUG: Datastore not ready, failed task discovery")
		}
		return false
	}

	// Read list of tasks
	k := fmt.Sprintf("%s~task_ids", instanceId)
	entry, err := datastore.GetEntry(k)
	if err != nil {
		log.Println(fmt.Sprintf("ERROR: Failed to discover tasks: %s", err))
		return false
	}
	if entry == nil {
		// No entry, nothing to do, but that's fine
		return true
	}

	// List of task ids
	taskIds := strings.Split(entry.Value, ",")
	for _, taskId := range taskIds {
		// Skip empty ones
		if len(taskId) == 0 {
			continue
		}

		// Skip imported ones
		_, found := td.executionCache.Get(taskId)
		if found {
			continue
		}

		// Skip completed ones
		doneFlag, _ := datastore.GetEntry(GetCompletionKey(taskId))
		if doneFlag != nil {
			continue
		}

		// Get task meta
		taskData, _ := datastore.GetEntry(fmt.Sprintf("task~%s", taskId))
		if taskData == nil {
			continue
		}

		// Decode
		lt := ReadLocalTask(taskData.Value)
		if lt == nil {
			continue
		}
		// Add to queue + flag as imported
		td.executionCache.Set(lt.Id, "1", 0)
		td.executionQueue <- lt
	}

	// Done
	return true
}

// Start task discoverer
func (td *TaskDiscoverer) Start() bool {
	// Discovery
	go func(td *TaskDiscoverer) {
		ticker := time.NewTicker(200 * time.Millisecond)
		for {
			select {
			case <-ticker.C:
				// Discover tasks
				td.Discover()
			case <-shutdown:
				ticker.Stop()
				return
			}
		}
	}(td)

	// Execution
	go func(td *TaskDiscoverer) {
		for {
			var lt *LocalTask
			lt = <-td.executionQueue
			lt.Run()
		}
	}(td)
	return true
}

// Write to datastore
func (t *Task) writeDatastore() bool {
	return datastore.PutEntry(fmt.Sprintf("task~%s", t.Id), t.toJson(), DATASTORE_TASK_TTL)
}

// Task to json
func (t *Task) toJson() string {
	b, err := json.Marshal(t)
	if err != nil {
		log.Println(fmt.Sprintf("ERR: Failed to format task in json: %s", err))
		return "{}"
	}
	return string(b)
}

// Execute node
func (t *Task) executeNode(node *Node) bool {
	// Require datastore
	if datastore == nil {
		log.Println("ERROR: Datastore not available, unable to execute task")
		return false
	}

	// Append this task ID to that nodes task list, trailing comma
	return datastore.AppendEntry(fmt.Sprintf("%s~task_ids", node.InstanceId), fmt.Sprintf("%s,", t.Id), DATASTORE_TASK_TTL)
}

// Execute task
func (t *Task) Execute() string {
	// Add task
	if t.writeDatastore() == false {
		log.Println(fmt.Sprintf("ERROR: Failed to write task %s to datastore", t.Id))
		return "-1"
	}

	// Add to node queues
	log.Println(fmt.Sprintf("INFO: Executing task %s with %d command(s) on %d node(s)", t.Id, len(t.Commands), len(t.targets)))
	for _, node := range t.targets {
		if t.executeNode(node) == false {
			log.Println(fmt.Sprintf("ERROR: Failed executing task %s on %s", t.Id, node.FullName()))
		}
	}
	return t.Id
}

// Local task
func ReadLocalTask(str string) *LocalTask {
	var lt *LocalTask = &LocalTask{}
	err := json.Unmarshal([]byte(str), &lt)
	if err != nil {
		log.Println(fmt.Sprintf("ERROR: Failed to decode local task json %s", err))
		return nil
	}
	return lt
}

// New task
func NewTask() *Task {
	return &Task{
		Id:       getUuid(),
		targets:  nil,
		Commands: make([]string, 0),
	}
}
<commit_msg>Enable shebang<commit_after>// @author Robin Verlangen
// API handler

package main

// Imports
import (
	"encoding/json"
	"fmt"
	"github.com/pmylund/go-cache"
	"log"
	"os"
	"os/exec"
	"strings"
	"time"
)

const DATASTORE_TASK_TTL = "86400" // 24 hours

// Task struct
type Task struct {
	Id               string   // Unique task id
	targets          []*Node  // List of target nodes
	Commands         []string // List of commands to execute in that order
	CreatedTimestamp int64    //  Created
}

// Local task (on a specific node)
type LocalTask struct {
	Id       string   // Unique task id
	Commands []string // List of commands to execute in that order
}

// Task discoverer
type TaskDiscoverer struct {
	executionQueue chan *LocalTask // Queue of tasks to be executed
	executionCache *cache.Cache    // Cache of executed task ids
}

// New task discoverer
func NewTaskDiscoverer() *TaskDiscoverer {
	return &TaskDiscoverer{
		executionQueue: make(chan *LocalTask, 1000),
		executionCache: cache.New(60*time.Minute, 30*time.Second),
	}
}

// Task completion key
func GetCompletionKey(taskId string) string {
	return fmt.Sprintf("task~%s~completed~%s", taskId, instanceId)
}

// Task completion key
func GetOutputKey(taskId string) string {
	return fmt.Sprintf("task~%s~output~%s", taskId, instanceId)
}

// Save output
func (lt *LocalTask) SaveOutput(output string) bool {
	return datastore.PutEntry(GetOutputKey(lt.Id), output, DATASTORE_TASK_TTL)
}

// Run local task
func (lt *LocalTask) Run() bool {
	// Task file
	var tmpFolder string = "/tmp" // @todo Configure
	var shell string = "sh"       // @todo Configure
	var tmpFile string = fmt.Sprintf("%s/%s", tmpFolder, lt.Id)
	file, fopenErr := os.OpenFile(tmpFile, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666)
	if fopenErr != nil {
		log.Println(fmt.Sprintf("ERROR: Failed to open tmp task file %s", fopenErr))
		return false
	}
	if debug {
		log.Println(fmt.Sprintf("DEBUG: Opened tmp task file in %s", file.Name()))
	}

	// Print commands to file
	file.WriteString(fmt.Sprintf("#!/bin/%s\n", shell))
	for _, cmd := range lt.Commands {
		file.WriteString(cmd + "\n")
		if debug {
			log.Println(fmt.Sprintf("Writing to '%s': %s", file.Name(), cmd))
		}
	}
	file.Sync()
	file.Close()

	// Execute file
	output, execErr := exec.Command(shell, file.Name()).Output()
	datastore.PutEntry(GetCompletionKey(lt.Id), "1", DATASTORE_TASK_TTL)
	if execErr != nil {
		log.Println(fmt.Sprintf("ERR: Failed to execute tmp task file: %s", execErr))
		return false
	}
	lt.SaveOutput(string(output))
	if debug {
		log.Println(fmt.Sprintf("DEBUG: Task output: %s", output))
	}

	// Cleanup
	removeErr := os.Remove(file.Name())
	if removeErr != nil {
		log.Println(fmt.Sprintf("ERR: Failed to remove tmp task file: %s", removeErr))
		return false
	}
	if debug {
		log.Println(fmt.Sprintf("DEBUG: Removed tmp task file in %s", file.Name()))
	}

	return false
}

// Discover new tasks from queue
func (td *TaskDiscoverer) Discover() bool {
	// Datastore ready?
	if datastore == nil {
		if debug {
			log.Println("DEBUG: Datastore not ready, failed task discovery")
		}
		return false
	}

	// Read list of tasks
	k := fmt.Sprintf("%s~task_ids", instanceId)
	entry, err := datastore.GetEntry(k)
	if err != nil {
		log.Println(fmt.Sprintf("ERROR: Failed to discover tasks: %s", err))
		return false
	}
	if entry == nil {
		// No entry, nothing to do, but that's fine
		return true
	}

	// List of task ids
	taskIds := strings.Split(entry.Value, ",")
	for _, taskId := range taskIds {
		// Skip empty ones
		if len(taskId) == 0 {
			continue
		}

		// Skip imported ones
		_, found := td.executionCache.Get(taskId)
		if found {
			continue
		}

		// Skip completed ones
		doneFlag, _ := datastore.GetEntry(GetCompletionKey(taskId))
		if doneFlag != nil {
			continue
		}

		// Get task meta
		taskData, _ := datastore.GetEntry(fmt.Sprintf("task~%s", taskId))
		if taskData == nil {
			continue
		}

		// Decode
		lt := ReadLocalTask(taskData.Value)
		if lt == nil {
			continue
		}
		// Add to queue + flag as imported
		td.executionCache.Set(lt.Id, "1", 0)
		td.executionQueue <- lt
	}

	// Done
	return true
}

// Start task discoverer
func (td *TaskDiscoverer) Start() bool {
	// Discovery
	go func(td *TaskDiscoverer) {
		ticker := time.NewTicker(200 * time.Millisecond)
		for {
			select {
			case <-ticker.C:
				// Discover tasks
				td.Discover()
			case <-shutdown:
				ticker.Stop()
				return
			}
		}
	}(td)

	// Execution
	go func(td *TaskDiscoverer) {
		for {
			var lt *LocalTask
			lt = <-td.executionQueue
			lt.Run()
		}
	}(td)
	return true
}

// Write to datastore
func (t *Task) writeDatastore() bool {
	return datastore.PutEntry(fmt.Sprintf("task~%s", t.Id), t.toJson(), DATASTORE_TASK_TTL)
}

// Task to json
func (t *Task) toJson() string {
	b, err := json.Marshal(t)
	if err != nil {
		log.Println(fmt.Sprintf("ERR: Failed to format task in json: %s", err))
		return "{}"
	}
	return string(b)
}

// Execute node
func (t *Task) executeNode(node *Node) bool {
	// Require datastore
	if datastore == nil {
		log.Println("ERROR: Datastore not available, unable to execute task")
		return false
	}

	// Append this task ID to that nodes task list, trailing comma
	return datastore.AppendEntry(fmt.Sprintf("%s~task_ids", node.InstanceId), fmt.Sprintf("%s,", t.Id), DATASTORE_TASK_TTL)
}

// Execute task
func (t *Task) Execute() string {
	// Add task
	if t.writeDatastore() == false {
		log.Println(fmt.Sprintf("ERROR: Failed to write task %s to datastore", t.Id))
		return "-1"
	}

	// Add to node queues
	log.Println(fmt.Sprintf("INFO: Executing task %s with %d command(s) on %d node(s)", t.Id, len(t.Commands), len(t.targets)))
	for _, node := range t.targets {
		if t.executeNode(node) == false {
			log.Println(fmt.Sprintf("ERROR: Failed executing task %s on %s", t.Id, node.FullName()))
		}
	}
	return t.Id
}

// Local task
func ReadLocalTask(str string) *LocalTask {
	var lt *LocalTask = &LocalTask{}
	err := json.Unmarshal([]byte(str), &lt)
	if err != nil {
		log.Println(fmt.Sprintf("ERROR: Failed to decode local task json %s", err))
		return nil
	}
	return lt
}

// New task
func NewTask() *Task {
	return &Task{
		Id:       getUuid(),
		targets:  nil,
		Commands: make([]string, 0),
	}
}
<|endoftext|>


<commit_before>package task

import (
	"fmt"
	"log"
	"os"
	"os/exec"
	"strings"

	"github.com/spf13/pflag"
)

var (
	// TaskFilePath is the default Taskfile
	TaskFilePath = "Taskfile"
	// ShExists is true if Bash was found
	ShExists bool
	// ShPath constains the Bash path if found
	ShPath string

	// Force (--force or -f flag) forces a task to run even when it's up-to-date
	Force bool

	// Tasks constains the tasks parsed from Taskfile
	Tasks = make(map[string]*Task)

	runnedTasks = make(map[string]struct{})
)

func init() {
	var err error
	ShPath, err = exec.LookPath("sh")
	if err != nil {
		return
	}
	ShExists = true
}

// Task represents a task
type Task struct {
	Cmds      []string
	Deps      []string
	Sources   []string
	Generates []string
	Dir       string
	Vars      map[string]string
	Set       string
	Env       map[string]string
}

// Run runs Task
func Run() {
	log.SetFlags(0)

	args := pflag.Args()
	if len(args) == 0 {
		log.Fatal("task: No argument given")
	}

	var err error
	Tasks, err = readTaskfile()
	if err != nil {
		log.Fatal(err)
	}

	for _, a := range args {
		if err = RunTask(a); err != nil {
			log.Fatal(err)
		}
	}
}

// RunTask runs a task by its name
func RunTask(name string) error {
	if _, found := runnedTasks[name]; found {
		return &cyclicDepError{name}
	}
	runnedTasks[name] = struct{}{}

	t, ok := Tasks[name]
	if !ok {
		return &taskNotFoundError{name}
	}

	vars, err := t.handleVariables()
	if err != nil {
		return &taskRunError{name, err}
	}

	for _, d := range t.Deps {
		d, err = ReplaceVariables(d, vars)
		if err != nil {
			return err
		}
		if err = RunTask(d); err != nil {
			return err
		}
	}

	if !Force && t.isUpToDate() {
		log.Printf(`task: Task "%s" is up to date`, name)
		return nil
	}

	for i := range t.Cmds {
		if err = t.runCommand(i); err != nil {
			return &taskRunError{name, err}
		}
	}
	return nil
}

func (t *Task) isUpToDate() bool {
	if len(t.Sources) == 0 || len(t.Generates) == 0 {
		return false
	}

	sourcesMaxTime, err := getPatternsMaxTime(t.Sources)
	if err != nil || sourcesMaxTime.IsZero() {
		return false
	}

	generatesMinTime, err := getPatternsMinTime(t.Generates)
	if err != nil || generatesMinTime.IsZero() {
		return false
	}

	return generatesMinTime.After(sourcesMaxTime)
}

func (t *Task) runCommand(i int) error {
	vars, err := t.handleVariables()
	if err != nil {
		return err
	}
	c, err := ReplaceVariables(t.Cmds[i], vars)
	if err != nil {
		return err
	}
	dir, err := ReplaceVariables(t.Dir, vars)
	if err != nil {
		return err
	}
	var cmd *exec.Cmd
	if ShExists {
		cmd = exec.Command(ShPath, "-c", c)
	} else {
		cmd = exec.Command("cmd", "/C", c)
	}
	if dir != "" {
		cmd.Dir = dir
	}
	if t.Env != nil {
		cmd.Env = os.Environ()
		for key, value := range t.Env {
			replacedValue, err := ReplaceVariables(value, vars)
			if err != nil {
				return err
			}
			replacedKey, err := ReplaceVariables(key, vars)
			if err != nil {
				return err
			}
			cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", replacedKey, replacedValue))
		}
	}
	cmd.Stdin = os.Stdin
	cmd.Stderr = os.Stderr
	if t.Set != "" {
		bytes, err := cmd.Output()
		if err != nil {
			return err
		}
		os.Setenv(t.Set, strings.TrimSpace(string(bytes)))
		return nil
	}
	cmd.Stdout = os.Stdout
	if err = cmd.Run(); err != nil {
		return err
	}
	return nil
}
<commit_msg>Support for a default branch<commit_after>package task

import (
	"fmt"
	"log"
	"os"
	"os/exec"
	"strings"

	"github.com/spf13/pflag"
)

var (
	// TaskFilePath is the default Taskfile
	TaskFilePath = "Taskfile"
	// ShExists is true if Bash was found
	ShExists bool
	// ShPath constains the Bash path if found
	ShPath string

	// Force (--force or -f flag) forces a task to run even when it's up-to-date
	Force bool

	// Tasks constains the tasks parsed from Taskfile
	Tasks = make(map[string]*Task)

	runnedTasks = make(map[string]struct{})
)

func init() {
	var err error
	ShPath, err = exec.LookPath("sh")
	if err != nil {
		return
	}
	ShExists = true
}

// Task represents a task
type Task struct {
	Cmds      []string
	Deps      []string
	Sources   []string
	Generates []string
	Dir       string
	Vars      map[string]string
	Set       string
	Env       map[string]string
}

// Run runs Task
func Run() {
	log.SetFlags(0)

	args := pflag.Args()
	if len(args) == 0 {
		log.Println("task: No argument given, trying default task")
		args = []string{"default"}
	}

	var err error
	Tasks, err = readTaskfile()
	if err != nil {
		log.Fatal(err)
	}

	for _, a := range args {
		if err = RunTask(a); err != nil {
			log.Fatal(err)
		}
	}
}

// RunTask runs a task by its name
func RunTask(name string) error {
	if _, found := runnedTasks[name]; found {
		return &cyclicDepError{name}
	}
	runnedTasks[name] = struct{}{}

	t, ok := Tasks[name]
	if !ok {
		return &taskNotFoundError{name}
	}

	vars, err := t.handleVariables()
	if err != nil {
		return &taskRunError{name, err}
	}

	for _, d := range t.Deps {
		d, err = ReplaceVariables(d, vars)
		if err != nil {
			return err
		}
		if err = RunTask(d); err != nil {
			return err
		}
	}

	if !Force && t.isUpToDate() {
		log.Printf(`task: Task "%s" is up to date`, name)
		return nil
	}

	for i := range t.Cmds {
		if err = t.runCommand(i); err != nil {
			return &taskRunError{name, err}
		}
	}
	return nil
}

func (t *Task) isUpToDate() bool {
	if len(t.Sources) == 0 || len(t.Generates) == 0 {
		return false
	}

	sourcesMaxTime, err := getPatternsMaxTime(t.Sources)
	if err != nil || sourcesMaxTime.IsZero() {
		return false
	}

	generatesMinTime, err := getPatternsMinTime(t.Generates)
	if err != nil || generatesMinTime.IsZero() {
		return false
	}

	return generatesMinTime.After(sourcesMaxTime)
}

func (t *Task) runCommand(i int) error {
	vars, err := t.handleVariables()
	if err != nil {
		return err
	}
	c, err := ReplaceVariables(t.Cmds[i], vars)
	if err != nil {
		return err
	}
	dir, err := ReplaceVariables(t.Dir, vars)
	if err != nil {
		return err
	}
	var cmd *exec.Cmd
	if ShExists {
		cmd = exec.Command(ShPath, "-c", c)
	} else {
		cmd = exec.Command("cmd", "/C", c)
	}
	if dir != "" {
		cmd.Dir = dir
	}
	if t.Env != nil {
		cmd.Env = os.Environ()
		for key, value := range t.Env {
			replacedValue, err := ReplaceVariables(value, vars)
			if err != nil {
				return err
			}
			replacedKey, err := ReplaceVariables(key, vars)
			if err != nil {
				return err
			}
			cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", replacedKey, replacedValue))
		}
	}
	cmd.Stdin = os.Stdin
	cmd.Stderr = os.Stderr
	if t.Set != "" {
		bytes, err := cmd.Output()
		if err != nil {
			return err
		}
		os.Setenv(t.Set, strings.TrimSpace(string(bytes)))
		return nil
	}
	cmd.Stdout = os.Stdout
	if err = cmd.Run(); err != nil {
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>package task

import (
	"context"
	"fmt"
	"io"
	"os"
	"strings"
	"sync/atomic"

	"github.com/go-task/task/v2/internal/compiler"
	compilerv1 "github.com/go-task/task/v2/internal/compiler/v1"
	compilerv2 "github.com/go-task/task/v2/internal/compiler/v2"
	"github.com/go-task/task/v2/internal/execext"
	"github.com/go-task/task/v2/internal/logger"
	"github.com/go-task/task/v2/internal/output"
	"github.com/go-task/task/v2/internal/taskfile"
	"github.com/go-task/task/v2/internal/taskfile/read"
	"github.com/go-task/task/v2/internal/taskfile/version"

	"github.com/Masterminds/semver"
	"golang.org/x/sync/errgroup"
)

const (
	// MaximumTaskCall is the max number of times a task can be called.
	// This exists to prevent infinite loops on cyclic dependencies
	MaximumTaskCall = 100
)

// Executor executes a Taskfile
type Executor struct {
	Taskfile *taskfile.Taskfile
	Dir      string
	Force    bool
	Watch    bool
	Verbose  bool
	Silent   bool
	Dry      bool
	Summary  bool

	Stdin  io.Reader
	Stdout io.Writer
	Stderr io.Writer

	Logger      *logger.Logger
	Compiler    compiler.Compiler
	Output      output.Output
	OutputStyle string

	taskvars taskfile.Vars

	taskCallCount map[string]*int32
}

// Run runs Task
func (e *Executor) Run(ctx context.Context, calls ...taskfile.Call) error {
	// check if given tasks exist
	for _, c := range calls {
		if _, ok := e.Taskfile.Tasks[c.Task]; !ok {
			// FIXME: move to the main package
			e.PrintTasksHelp()
			return &taskNotFoundError{taskName: c.Task}
		}
	}

	if e.Summary {
		e.printTaskSummary(calls[0].Task)
		return nil
	}

	if e.Watch {
		return e.watchTasks(calls...)
	}

	for _, c := range calls {
		if err := e.RunTask(ctx, c); err != nil {
			return err
		}
	}
	return nil
}

func (e *Executor) printTaskSummary(task string) {
	t := e.Taskfile.Tasks[task]
	s := t.Summary
	if s == "" {
		e.Logger.Errf("task: There is no detailed description for task: %s", task)
		return
	}

	e.Logger.Outf("task: " + task)
	e.Logger.Outf("")

	printTaskDetailedDescription(s, e.Logger)
	printTaskDependencies(t.Deps, e.Logger)

	e.Logger.Outf("")
	printCommands(t.Cmds, e.Logger)
}

func printCommands(cmds []*taskfile.Cmd, logger *logger.Logger) {
	logger.Outf("commands:")
	for _, c := range cmds {
		logger.Outf(" - %s", c.Cmd)
	}
}

func printTaskDependencies(deps []*taskfile.Dep, logger *logger.Logger) {
	hasDependencies := len(deps) > 0
	if hasDependencies {
		logger.Outf("")
		logger.Outf("dependencies:")

		for _, d := range deps {
			logger.Outf(" - %s", d.Task)
		}
	}
}

func printTaskDetailedDescription(description string, Logger *logger.Logger) {
	lines := strings.Split(description, "\n")
	for i, line := range lines {
		notLastLine := i+1 < len(lines)
		if notLastLine || line != "" {
			Logger.Outf(line)
		}
	}

}

// Setup setups Executor's internal state
func (e *Executor) Setup() error {
	var err error
	e.Taskfile, err = read.Taskfile(e.Dir)
	if err != nil {
		return err
	}
	e.taskvars, err = read.Taskvars(e.Dir)
	if err != nil {
		return err
	}

	v, err := semver.NewConstraint(e.Taskfile.Version)
	if err != nil {
		return fmt.Errorf(`task: could not parse taskfile version "%s": %v`, e.Taskfile.Version, err)
	}

	if e.Stdin == nil {
		e.Stdin = os.Stdin
	}
	if e.Stdout == nil {
		e.Stdout = os.Stdout
	}
	if e.Stderr == nil {
		e.Stderr = os.Stderr
	}
	e.Logger = &logger.Logger{
		Stdout:  e.Stdout,
		Stderr:  e.Stderr,
		Verbose: e.Verbose,
	}
	switch {
	case version.IsV1(v):
		e.Compiler = &compilerv1.CompilerV1{
			Dir:    e.Dir,
			Vars:   e.taskvars,
			Logger: e.Logger,
		}
	case version.IsV2(v), version.IsV21(v), version.IsV22(v):
		e.Compiler = &compilerv2.CompilerV2{
			Dir:          e.Dir,
			Taskvars:     e.taskvars,
			TaskfileVars: e.Taskfile.Vars,
			Expansions:   e.Taskfile.Expansions,
			Logger:       e.Logger,
		}
	case version.IsV23(v):
		return fmt.Errorf(`task: Taskfile versions greater than v2.3 not implemented in the version of Task`)
	}

	if !version.IsV21(v) && e.Taskfile.Output != "" {
		return fmt.Errorf(`task: Taskfile option "output" is only available starting on Taskfile version v2.1`)
	}
	if !version.IsV22(v) && len(e.Taskfile.Includes) > 0 {
		return fmt.Errorf(`task: Including Taskfiles is only available starting on Taskfile version v2.2`)
	}
	if e.OutputStyle != "" {
		e.Taskfile.Output = e.OutputStyle
	}
	switch e.Taskfile.Output {
	case "", "interleaved":
		e.Output = output.Interleaved{}
	case "group":
		e.Output = output.Group{}
	case "prefixed":
		e.Output = output.Prefixed{}
	default:
		return fmt.Errorf(`task: output option "%s" not recognized`, e.Taskfile.Output)
	}

	if !version.IsV21(v) {
		err := fmt.Errorf(`task: Taskfile option "ignore_error" is only available starting on Taskfile version v2.1`)

		for _, task := range e.Taskfile.Tasks {
			if task.IgnoreError {
				return err
			}
			for _, cmd := range task.Cmds {
				if cmd.IgnoreError {
					return err
				}
			}
		}
	}

	e.taskCallCount = make(map[string]*int32, len(e.Taskfile.Tasks))
	for k := range e.Taskfile.Tasks {
		e.taskCallCount[k] = new(int32)
	}
	return nil
}

// RunTask runs a task by its name
func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error {
	t, err := e.CompiledTask(call)
	if err != nil {
		return err
	}
	if !e.Watch && atomic.AddInt32(e.taskCallCount[call.Task], 1) >= MaximumTaskCall {
		return &MaximumTaskCallExceededError{task: call.Task}
	}

	if err := e.runDeps(ctx, t); err != nil {
		return err
	}

	if !e.Force {
		upToDate, err := e.isTaskUpToDate(ctx, t)
		if err != nil {
			return err
		}
		if upToDate {
			if !e.Silent {
				e.Logger.Errf(`task: Task "%s" is up to date`, t.Task)
			}
			return nil
		}
	}

	for i := range t.Cmds {
		if err := e.runCommand(ctx, t, call, i); err != nil {
			if err2 := e.statusOnError(t); err2 != nil {
				e.Logger.VerboseErrf("task: error cleaning status on error: %v", err2)
			}

			if execext.IsExitError(err) && t.IgnoreError {
				e.Logger.VerboseErrf("task: task error ignored: %v", err)
				continue
			}

			return &taskRunError{t.Task, err}
		}
	}
	return nil
}

func (e *Executor) runDeps(ctx context.Context, t *taskfile.Task) error {
	g, ctx := errgroup.WithContext(ctx)

	for _, d := range t.Deps {
		d := d

		g.Go(func() error {
			return e.RunTask(ctx, taskfile.Call{Task: d.Task, Vars: d.Vars})
		})
	}

	return g.Wait()
}

func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfile.Call, i int) error {
	cmd := t.Cmds[i]

	switch {
	case cmd.Task != "":
		return e.RunTask(ctx, taskfile.Call{Task: cmd.Task, Vars: cmd.Vars})
	case cmd.Cmd != "":
		if e.Verbose || (!cmd.Silent && !t.Silent && !e.Silent) {
			e.Logger.Errf(cmd.Cmd)
		}

		if e.Dry {
			return nil
		}

		stdOut := e.Output.WrapWriter(e.Stdout, t.Prefix)
		stdErr := e.Output.WrapWriter(e.Stderr, t.Prefix)
		defer stdOut.Close()
		defer stdErr.Close()

		err := execext.RunCommand(ctx, &execext.RunCommandOptions{
			Command: cmd.Cmd,
			Dir:     t.Dir,
			Env:     getEnviron(t),
			Stdin:   e.Stdin,
			Stdout:  stdOut,
			Stderr:  stdErr,
		})
		if execext.IsExitError(err) && cmd.IgnoreError {
			e.Logger.VerboseErrf("task: command error ignored: %v", err)
			return nil
		}
		return err
	default:
		return nil
	}
}

func getEnviron(t *taskfile.Task) []string {
	if t.Env == nil {
		return nil
	}

	environ := os.Environ()
	for k, v := range t.Env.ToStringMap() {
		environ = append(environ, fmt.Sprintf("%s=%s", k, v))
	}
	return environ
}
<commit_msg>refactoring<commit_after>package task

import (
	"context"
	"fmt"
	"io"
	"os"
	"strings"
	"sync/atomic"

	"github.com/go-task/task/v2/internal/compiler"
	compilerv1 "github.com/go-task/task/v2/internal/compiler/v1"
	compilerv2 "github.com/go-task/task/v2/internal/compiler/v2"
	"github.com/go-task/task/v2/internal/execext"
	"github.com/go-task/task/v2/internal/logger"
	"github.com/go-task/task/v2/internal/output"
	"github.com/go-task/task/v2/internal/taskfile"
	"github.com/go-task/task/v2/internal/taskfile/read"
	"github.com/go-task/task/v2/internal/taskfile/version"

	"github.com/Masterminds/semver"
	"golang.org/x/sync/errgroup"
)

const (
	// MaximumTaskCall is the max number of times a task can be called.
	// This exists to prevent infinite loops on cyclic dependencies
	MaximumTaskCall = 100
)

// Executor executes a Taskfile
type Executor struct {
	Taskfile *taskfile.Taskfile
	Dir      string
	Force    bool
	Watch    bool
	Verbose  bool
	Silent   bool
	Dry      bool
	Summary  bool

	Stdin  io.Reader
	Stdout io.Writer
	Stderr io.Writer

	Logger      *logger.Logger
	Compiler    compiler.Compiler
	Output      output.Output
	OutputStyle string

	taskvars taskfile.Vars

	taskCallCount map[string]*int32
}

// Run runs Task
func (e *Executor) Run(ctx context.Context, calls ...taskfile.Call) error {
	// check if given tasks exist
	for _, c := range calls {
		if _, ok := e.Taskfile.Tasks[c.Task]; !ok {
			// FIXME: move to the main package
			e.PrintTasksHelp()
			return &taskNotFoundError{taskName: c.Task}
		}
	}

	if e.Summary {
		e.printTaskSummary(calls[0].Task)
		return nil
	}

	if e.Watch {
		return e.watchTasks(calls...)
	}

	for _, c := range calls {
		if err := e.RunTask(ctx, c); err != nil {
			return err
		}
	}
	return nil
}

func (e *Executor) printTaskSummary(task string) {
	t := e.Taskfile.Tasks[task]
	s := t.Summary
	if s == "" {
		e.Logger.Errf("task: There is no detailed description for task: %s", task)
		return
	}

	e.Logger.Outf("task: " + task)
	e.Logger.Outf("")

	printTaskSummary(s, e.Logger)
	printTaskDependencies(t.Deps, e.Logger)
	printCommands(t.Cmds, e.Logger)
}

func printCommands(cmds []*taskfile.Cmd, logger *logger.Logger) {
	logger.Outf("commands:")
	for _, c := range cmds {
		logger.Outf(" - %s", c.Cmd)
	}
}

func printTaskDependencies(deps []*taskfile.Dep, logger *logger.Logger) {
	hasDependencies := len(deps) > 0
	if hasDependencies {
		logger.Outf("")
		logger.Outf("dependencies:")

		for _, d := range deps {
			logger.Outf(" - %s", d.Task)
		}
	}
	logger.Outf("")
}

func printTaskSummary(description string, Logger *logger.Logger) {
	lines := strings.Split(description, "\n")
	for i, line := range lines {
		notLastLine := i+1 < len(lines)
		if notLastLine || line != "" {
			Logger.Outf(line)
		}
	}
}

// Setup setups Executor's internal state
func (e *Executor) Setup() error {
	var err error
	e.Taskfile, err = read.Taskfile(e.Dir)
	if err != nil {
		return err
	}
	e.taskvars, err = read.Taskvars(e.Dir)
	if err != nil {
		return err
	}

	v, err := semver.NewConstraint(e.Taskfile.Version)
	if err != nil {
		return fmt.Errorf(`task: could not parse taskfile version "%s": %v`, e.Taskfile.Version, err)
	}

	if e.Stdin == nil {
		e.Stdin = os.Stdin
	}
	if e.Stdout == nil {
		e.Stdout = os.Stdout
	}
	if e.Stderr == nil {
		e.Stderr = os.Stderr
	}
	e.Logger = &logger.Logger{
		Stdout:  e.Stdout,
		Stderr:  e.Stderr,
		Verbose: e.Verbose,
	}
	switch {
	case version.IsV1(v):
		e.Compiler = &compilerv1.CompilerV1{
			Dir:    e.Dir,
			Vars:   e.taskvars,
			Logger: e.Logger,
		}
	case version.IsV2(v), version.IsV21(v), version.IsV22(v):
		e.Compiler = &compilerv2.CompilerV2{
			Dir:          e.Dir,
			Taskvars:     e.taskvars,
			TaskfileVars: e.Taskfile.Vars,
			Expansions:   e.Taskfile.Expansions,
			Logger:       e.Logger,
		}
	case version.IsV23(v):
		return fmt.Errorf(`task: Taskfile versions greater than v2.3 not implemented in the version of Task`)
	}

	if !version.IsV21(v) && e.Taskfile.Output != "" {
		return fmt.Errorf(`task: Taskfile option "output" is only available starting on Taskfile version v2.1`)
	}
	if !version.IsV22(v) && len(e.Taskfile.Includes) > 0 {
		return fmt.Errorf(`task: Including Taskfiles is only available starting on Taskfile version v2.2`)
	}
	if e.OutputStyle != "" {
		e.Taskfile.Output = e.OutputStyle
	}
	switch e.Taskfile.Output {
	case "", "interleaved":
		e.Output = output.Interleaved{}
	case "group":
		e.Output = output.Group{}
	case "prefixed":
		e.Output = output.Prefixed{}
	default:
		return fmt.Errorf(`task: output option "%s" not recognized`, e.Taskfile.Output)
	}

	if !version.IsV21(v) {
		err := fmt.Errorf(`task: Taskfile option "ignore_error" is only available starting on Taskfile version v2.1`)

		for _, task := range e.Taskfile.Tasks {
			if task.IgnoreError {
				return err
			}
			for _, cmd := range task.Cmds {
				if cmd.IgnoreError {
					return err
				}
			}
		}
	}

	e.taskCallCount = make(map[string]*int32, len(e.Taskfile.Tasks))
	for k := range e.Taskfile.Tasks {
		e.taskCallCount[k] = new(int32)
	}
	return nil
}

// RunTask runs a task by its name
func (e *Executor) RunTask(ctx context.Context, call taskfile.Call) error {
	t, err := e.CompiledTask(call)
	if err != nil {
		return err
	}
	if !e.Watch && atomic.AddInt32(e.taskCallCount[call.Task], 1) >= MaximumTaskCall {
		return &MaximumTaskCallExceededError{task: call.Task}
	}

	if err := e.runDeps(ctx, t); err != nil {
		return err
	}

	if !e.Force {
		upToDate, err := e.isTaskUpToDate(ctx, t)
		if err != nil {
			return err
		}
		if upToDate {
			if !e.Silent {
				e.Logger.Errf(`task: Task "%s" is up to date`, t.Task)
			}
			return nil
		}
	}

	for i := range t.Cmds {
		if err := e.runCommand(ctx, t, call, i); err != nil {
			if err2 := e.statusOnError(t); err2 != nil {
				e.Logger.VerboseErrf("task: error cleaning status on error: %v", err2)
			}

			if execext.IsExitError(err) && t.IgnoreError {
				e.Logger.VerboseErrf("task: task error ignored: %v", err)
				continue
			}

			return &taskRunError{t.Task, err}
		}
	}
	return nil
}

func (e *Executor) runDeps(ctx context.Context, t *taskfile.Task) error {
	g, ctx := errgroup.WithContext(ctx)

	for _, d := range t.Deps {
		d := d

		g.Go(func() error {
			return e.RunTask(ctx, taskfile.Call{Task: d.Task, Vars: d.Vars})
		})
	}

	return g.Wait()
}

func (e *Executor) runCommand(ctx context.Context, t *taskfile.Task, call taskfile.Call, i int) error {
	cmd := t.Cmds[i]

	switch {
	case cmd.Task != "":
		return e.RunTask(ctx, taskfile.Call{Task: cmd.Task, Vars: cmd.Vars})
	case cmd.Cmd != "":
		if e.Verbose || (!cmd.Silent && !t.Silent && !e.Silent) {
			e.Logger.Errf(cmd.Cmd)
		}

		if e.Dry {
			return nil
		}

		stdOut := e.Output.WrapWriter(e.Stdout, t.Prefix)
		stdErr := e.Output.WrapWriter(e.Stderr, t.Prefix)
		defer stdOut.Close()
		defer stdErr.Close()

		err := execext.RunCommand(ctx, &execext.RunCommandOptions{
			Command: cmd.Cmd,
			Dir:     t.Dir,
			Env:     getEnviron(t),
			Stdin:   e.Stdin,
			Stdout:  stdOut,
			Stderr:  stdErr,
		})
		if execext.IsExitError(err) && cmd.IgnoreError {
			e.Logger.VerboseErrf("task: command error ignored: %v", err)
			return nil
		}
		return err
	default:
		return nil
	}
}

func getEnviron(t *taskfile.Task) []string {
	if t.Env == nil {
		return nil
	}

	environ := os.Environ()
	for k, v := range t.Env.ToStringMap() {
		environ = append(environ, fmt.Sprintf("%s=%s", k, v))
	}
	return environ
}
<|endoftext|>


<commit_before>package slackapi

import (
	"encoding/json"
	"net/url"
	"strconv"
)

type TeamAccessLogsInput struct {
	// End of time range of logs to include in results (inclusive).
	Before string `json:"before"`
	// Number of items to return per page.
	Count int `json:"count"`
	// Page number of results to return.
	Page int `json:"page"`
	// Encoded team id to get logs from, required if org token is used.
	TeamID string `json:"team_id"`
}

// TeamAccessLogsResponse defines the JSON-encoded output for TeamAccessLogs.
type TeamAccessLogsResponse struct {
	Response
	Logins []AccessLog `json:"logins"`
}

// AccessLog defines the expected data from the JSON-encoded API response.
type AccessLog struct {
	UserID    string      `json:"user_id"`
	Username  string      `json:"username"`
	DateFirst json.Number `json:"date_first"`
	DateLast  json.Number `json:"date_last"`
	Count     int         `json:"count"`
	IP        string      `json:"ip"`
	UserAgent string      `json:"user_agent"`
	ISP       string      `json:"isp"`
	Country   string      `json:"country"`
	Region    string      `json:"region"`
}

// TeamAccessLogs is https://api.slack.com/methods/team.accessLogs
func (s *SlackAPI) TeamAccessLogs(input TeamAccessLogsInput) TeamAccessLogsResponse {
	in := url.Values{}

	if input.Before != "" {
		in.Add("before", input.Before)
	}

	if input.Count > 0 {
		in.Add("count", strconv.Itoa(input.Count))
	} else {
		in.Add("count", "100")
	}

	if input.Page > 0 {
		in.Add("page", strconv.Itoa(input.Page))
	}

	if input.TeamID != "" {
		in.Add("team_id", input.TeamID)
	}

	var out TeamAccessLogsResponse
	if err := s.baseGET("/api/team.accessLogs", in, &out); err != nil {
		return TeamAccessLogsResponse{Response: Response{Error: err.Error()}}
	}
	return out
}

type TeamBillableInfoResponse struct {
	Response
	BillableInfo map[string]BillableInfo `json:"billable_info"`
}

type BillableInfo struct {
	BillingActive bool `json:"billing_active"`
}

// TeamBillableInfo is https://api.slack.com/methods/team.billableInfo
func (s *SlackAPI) TeamBillableInfo(teamID string, user string) TeamBillableInfoResponse {
	in := url.Values{}

	if teamID != "" {
		in.Add("team_id", teamID)
	}

	if user != "" {
		in.Add("user", user)
	}

	var out TeamBillableInfoResponse
	if err := s.baseGET("/api/team.billableInfo", in, &out); err != nil {
		return TeamBillableInfoResponse{Response: Response{Error: err.Error()}}
	}
	return out
}

type TeamBillingInfoResponse struct {
	Response
	Plan string `json:"plan"`
}

// TeamBillingInfo is https://api.slack.com/methods/team.billing.info
func (s *SlackAPI) TeamBillingInfo() TeamBillingInfoResponse {
	in := url.Values{}
	var out TeamBillingInfoResponse
	if err := s.baseGET("/api/team.billing.info", in, &out); err != nil {
		return TeamBillingInfoResponse{Response: Response{Error: err.Error()}}
	}
	return out
}

type TeamInfoResponse struct {
	Response
	Team Team `json:"team"`
}

// Team defines the expected data from the JSON-encoded API response.
type Team struct {
	Domain      string   `json:"domain"`
	EmailDomain string   `json:"email_domain"`
	Icon        TeamIcon `json:"icon"`
	ID          string   `json:"id"`
	Name        string   `json:"name"`
}

// TeamIcon defines the expected data from the JSON-encoded API response.
type TeamIcon struct {
	Image102      string `json:"image_102"`
	Image132      string `json:"image_132"`
	Image34       string `json:"image_34"`
	Image44       string `json:"image_44"`
	Image68       string `json:"image_68"`
	Image88       string `json:"image_88"`
	ImageOriginal string `json:"image_original"`
}

// TeamInfo gets information about the current team.
//
// The team parameter is to get info on, if omitted, will return information
// about the current team. Will only return team that the authenticated token
// is allowed to see through external shared channels
func (s *SlackAPI) TeamInfo(team string) TeamInfoResponse {
	in := url.Values{}
	if team != "" {
		in.Add("team", team)
	}
	var out TeamInfoResponse
	if err := s.baseGET("/api/team.info", in, &out); err != nil {
		return TeamInfoResponse{Response: Response{Error: err.Error()}}
	}
	return out
}

type TeamIntegrationLogsInput struct {
	// Filter logs to this Slack app. Defaults to all logs.
	AppID string `json:"app_id"`
	// Filter logs with this change type. Defaults to all logs.
	ChangeType string `json:"change_type"`
	// Number of items to return per page.
	Count string `json:"count"`
	// Page number of results to return.
	Page string `json:"page"`
	// Filter logs to this service. Defaults to all logs.
	ServiceID string `json:"service_id"`
	// Encoded team id to get logs from, required if org token is used
	TeamID string `json:"team_id"`
	// Filter logs generated by this user’s actions. Defaults to all logs.
	User string `json:"user"`
}

// TeamIntegrationLogsResponse defines the JSON-encoded output for TeamIntegrationLogs.
type TeamIntegrationLogsResponse struct {
	Response
	Logs   []IntegrationLog `json:"logs"`
	Paging Paging           `json:"paging"`
}

type IntegrationLog struct {
	ServiceID   int    `json:"service_id"`
	ServiceType string `json:"service_type"`
	UserID      string `json:"user_id"`
	UserName    string `json:"user_name"`
	Channel     string `json:"channel"`
	Date        string `json:"date"`
	ChangeType  string `json:"change_type"`
	Scope       string `json:"scope"`
}

// TeamIntegrationLogs is https://api.slack.com/methods/team.integrationLogs
func (s *SlackAPI) TeamIntegrationLogs(input TeamIntegrationLogsInput) TeamIntegrationLogsResponse {
	in := url.Values{}

	if input.AppID != "" {
		in.Add("app_id", input.AppID)
	}

	if input.ChangeType != "" {
		in.Add("change_type", input.ChangeType)
	}

	if input.Count != "" {
		in.Add("count", input.Count)
	}

	if input.Page != "" {
		in.Add("page", input.Page)
	}

	if input.ServiceID != "" {
		in.Add("service_id", input.ServiceID)
	}

	if input.TeamID != "" {
		in.Add("team_id", input.TeamID)
	}

	if input.User != "" {
		in.Add("user", input.User)
	}

	var out TeamIntegrationLogsResponse
	if err := s.baseGET("/api/team.integrationLogs", in, &out); err != nil {
		return TeamIntegrationLogsResponse{Response: Response{Error: err.Error()}}
	}
	return out
}

// ResponseTeamProfile defines the JSON-encoded output for TeamProfile.
type ResponseTeamProfile struct {
	Response
	Profile TeamProfile `json:"profile"`
}

// TeamProfile defines the expected data from the JSON-encoded API response.
type TeamProfile struct {
	Fields []TeamProfileField `json:"fields"`
}

// TeamProfileField defines the expected data from the JSON-encoded API response.
type TeamProfileField struct {
	ID             string      `json:"id"`
	Ordering       int         `json:"ordering"`
	FieldName      string      `json:"field_name"`
	Label          string      `json:"label"`
	Hint           string      `json:"hint"`
	Type           string      `json:"type"`
	PossibleValues interface{} `json:"possible_values"`
	Options        interface{} `json:"options"`
	IsHidden       bool        `json:"is_hidden"`
}

// TeamProfileGet retrieve a team's profile.
func (s *SlackAPI) TeamProfileGet() ResponseTeamProfile {
	var response ResponseTeamProfile
	s.getRequest(&response, "team.profile.get", nil)
	return response
}
<commit_msg>Use new API in team.profile.get method implementation<commit_after>package slackapi

import (
	"encoding/json"
	"net/url"
	"strconv"
)

type TeamAccessLogsInput struct {
	// End of time range of logs to include in results (inclusive).
	Before string `json:"before"`
	// Number of items to return per page.
	Count int `json:"count"`
	// Page number of results to return.
	Page int `json:"page"`
	// Encoded team id to get logs from, required if org token is used.
	TeamID string `json:"team_id"`
}

// TeamAccessLogsResponse defines the JSON-encoded output for TeamAccessLogs.
type TeamAccessLogsResponse struct {
	Response
	Logins []AccessLog `json:"logins"`
}

// AccessLog defines the expected data from the JSON-encoded API response.
type AccessLog struct {
	UserID    string      `json:"user_id"`
	Username  string      `json:"username"`
	DateFirst json.Number `json:"date_first"`
	DateLast  json.Number `json:"date_last"`
	Count     int         `json:"count"`
	IP        string      `json:"ip"`
	UserAgent string      `json:"user_agent"`
	ISP       string      `json:"isp"`
	Country   string      `json:"country"`
	Region    string      `json:"region"`
}

// TeamAccessLogs is https://api.slack.com/methods/team.accessLogs
func (s *SlackAPI) TeamAccessLogs(input TeamAccessLogsInput) TeamAccessLogsResponse {
	in := url.Values{}

	if input.Before != "" {
		in.Add("before", input.Before)
	}

	if input.Count > 0 {
		in.Add("count", strconv.Itoa(input.Count))
	} else {
		in.Add("count", "100")
	}

	if input.Page > 0 {
		in.Add("page", strconv.Itoa(input.Page))
	}

	if input.TeamID != "" {
		in.Add("team_id", input.TeamID)
	}

	var out TeamAccessLogsResponse
	if err := s.baseGET("/api/team.accessLogs", in, &out); err != nil {
		return TeamAccessLogsResponse{Response: Response{Error: err.Error()}}
	}
	return out
}

type TeamBillableInfoResponse struct {
	Response
	BillableInfo map[string]BillableInfo `json:"billable_info"`
}

type BillableInfo struct {
	BillingActive bool `json:"billing_active"`
}

// TeamBillableInfo is https://api.slack.com/methods/team.billableInfo
func (s *SlackAPI) TeamBillableInfo(teamID string, user string) TeamBillableInfoResponse {
	in := url.Values{}

	if teamID != "" {
		in.Add("team_id", teamID)
	}

	if user != "" {
		in.Add("user", user)
	}

	var out TeamBillableInfoResponse
	if err := s.baseGET("/api/team.billableInfo", in, &out); err != nil {
		return TeamBillableInfoResponse{Response: Response{Error: err.Error()}}
	}
	return out
}

type TeamBillingInfoResponse struct {
	Response
	Plan string `json:"plan"`
}

// TeamBillingInfo is https://api.slack.com/methods/team.billing.info
func (s *SlackAPI) TeamBillingInfo() TeamBillingInfoResponse {
	in := url.Values{}
	var out TeamBillingInfoResponse
	if err := s.baseGET("/api/team.billing.info", in, &out); err != nil {
		return TeamBillingInfoResponse{Response: Response{Error: err.Error()}}
	}
	return out
}

type TeamInfoResponse struct {
	Response
	Team Team `json:"team"`
}

// Team defines the expected data from the JSON-encoded API response.
type Team struct {
	Domain      string   `json:"domain"`
	EmailDomain string   `json:"email_domain"`
	Icon        TeamIcon `json:"icon"`
	ID          string   `json:"id"`
	Name        string   `json:"name"`
}

// TeamIcon defines the expected data from the JSON-encoded API response.
type TeamIcon struct {
	Image102      string `json:"image_102"`
	Image132      string `json:"image_132"`
	Image34       string `json:"image_34"`
	Image44       string `json:"image_44"`
	Image68       string `json:"image_68"`
	Image88       string `json:"image_88"`
	ImageOriginal string `json:"image_original"`
}

// TeamInfo gets information about the current team.
//
// The team parameter is to get info on, if omitted, will return information
// about the current team. Will only return team that the authenticated token
// is allowed to see through external shared channels
func (s *SlackAPI) TeamInfo(team string) TeamInfoResponse {
	in := url.Values{}
	if team != "" {
		in.Add("team", team)
	}
	var out TeamInfoResponse
	if err := s.baseGET("/api/team.info", in, &out); err != nil {
		return TeamInfoResponse{Response: Response{Error: err.Error()}}
	}
	return out
}

type TeamIntegrationLogsInput struct {
	// Filter logs to this Slack app. Defaults to all logs.
	AppID string `json:"app_id"`
	// Filter logs with this change type. Defaults to all logs.
	ChangeType string `json:"change_type"`
	// Number of items to return per page.
	Count string `json:"count"`
	// Page number of results to return.
	Page string `json:"page"`
	// Filter logs to this service. Defaults to all logs.
	ServiceID string `json:"service_id"`
	// Encoded team id to get logs from, required if org token is used
	TeamID string `json:"team_id"`
	// Filter logs generated by this user’s actions. Defaults to all logs.
	User string `json:"user"`
}

// TeamIntegrationLogsResponse defines the JSON-encoded output for TeamIntegrationLogs.
type TeamIntegrationLogsResponse struct {
	Response
	Logs   []IntegrationLog `json:"logs"`
	Paging Paging           `json:"paging"`
}

type IntegrationLog struct {
	ServiceID   int    `json:"service_id"`
	ServiceType string `json:"service_type"`
	UserID      string `json:"user_id"`
	UserName    string `json:"user_name"`
	Channel     string `json:"channel"`
	Date        string `json:"date"`
	ChangeType  string `json:"change_type"`
	Scope       string `json:"scope"`
}

// TeamIntegrationLogs is https://api.slack.com/methods/team.integrationLogs
func (s *SlackAPI) TeamIntegrationLogs(input TeamIntegrationLogsInput) TeamIntegrationLogsResponse {
	in := url.Values{}

	if input.AppID != "" {
		in.Add("app_id", input.AppID)
	}

	if input.ChangeType != "" {
		in.Add("change_type", input.ChangeType)
	}

	if input.Count != "" {
		in.Add("count", input.Count)
	}

	if input.Page != "" {
		in.Add("page", input.Page)
	}

	if input.ServiceID != "" {
		in.Add("service_id", input.ServiceID)
	}

	if input.TeamID != "" {
		in.Add("team_id", input.TeamID)
	}

	if input.User != "" {
		in.Add("user", input.User)
	}

	var out TeamIntegrationLogsResponse
	if err := s.baseGET("/api/team.integrationLogs", in, &out); err != nil {
		return TeamIntegrationLogsResponse{Response: Response{Error: err.Error()}}
	}
	return out
}

// TeamProfileResponse defines the JSON-encoded output for TeamProfile.
type TeamProfileResponse struct {
	Response
	Profile TeamProfile `json:"profile"`
}

// TeamProfile defines the expected data from the JSON-encoded API response.
type TeamProfile struct {
	Fields []TeamProfileField `json:"fields"`
}

// TeamProfileField defines the expected data from the JSON-encoded API response.
type TeamProfileField struct {
	ID             string      `json:"id"`
	Ordering       int         `json:"ordering"`
	FieldName      string      `json:"field_name"`
	Label          string      `json:"label"`
	Hint           string      `json:"hint"`
	Type           string      `json:"type"`
	PossibleValues interface{} `json:"possible_values"`
	Options        interface{} `json:"options"`
	IsHidden       bool        `json:"is_hidden"`
}

// TeamProfileGet https://api.slack.com/methods/team.profile.get
func (s *SlackAPI) TeamProfileGet() TeamProfileResponse {
	in := url.Values{}
	var out TeamProfileResponse
	if err := s.baseGET("/api/team.profile.get", in, &out); err != nil {
		return TeamProfileResponse{Response: Response{Error: err.Error()}}
	}
	return out
}
<|endoftext|>


<commit_before>package main

import (
    //"fmt"
    "image"
    "image/color"
    //"image/gif"
    "os"
    "bufio"
    "io"
    "compress/lzw"
    "bytes"
    "math"
)

func writeHeader(w *bufio.Writer, m image.Image) {
    w.WriteByte('G')
    w.WriteByte('I')
    w.WriteByte('F')
    w.WriteByte('8')
    w.WriteByte('9')
    w.WriteByte('a')

    b := m.Bounds()
    w.WriteByte(byte(b.Max.X / 255)) // Image width, LSB.
    w.WriteByte(byte(b.Max.X % 255)) // Image width, MSB.
    w.WriteByte(byte(b.Max.Y / 255)) // Image height, LSB.
    w.WriteByte(byte(b.Max.Y % 255)) // Image height, MSB.

    w.WriteByte(byte(0xF7)) // GCT follows for 256 colors with resolution
                                 // 3 x 8 bits/primary
    w.WriteByte(byte(0x00)) // Background color.
    w.WriteByte(byte(0x00)) // Default pixel aspect ratio.

    // Grayscale color table.
    for i := 0; i < 256; i++ {
        w.WriteByte(byte(i)) // Repeat the same color value to get
        w.WriteByte(byte(i)) // grayscale.
        w.WriteByte(byte(i))
    }

    w.WriteByte(byte(0x21)) // Application Extension block.
    w.WriteByte(byte(0xF9)) // Application Extension block (cont).
    w.WriteByte(byte(0x0B)) // Next 11 bytes are Application Extension.
    w.WriteByte('N') // 8 Character application name.
    w.WriteByte('E')
    w.WriteByte('T')
    w.WriteByte('S')
    w.WriteByte('C')
    w.WriteByte('A')
    w.WriteByte('P')
    w.WriteByte('E')
    w.WriteByte('2')
    w.WriteByte('.')
    w.WriteByte('0')
    w.WriteByte(byte(0x03)) // 3 more bytes of Application Extension.
    w.WriteByte(byte(0x01)) // Data sub-block index (always 1).
    w.WriteByte(byte(0xFF)) // Number of repetitions, LSB.
    w.WriteByte(byte(0xFF)) // Number of repetitions, MSB.
    w.WriteByte(byte(0x00)) // End of Application Extension block.
}

func writeFrameHeader(w *bufio.Writer, m image.Image) {
    w.WriteByte(byte(0x21)) // Start of Graphic Control Extension.
    w.WriteByte(byte(0xF9)) // Start of Graphic Control Extension (cont).
    w.WriteByte(byte(0x04)) // 4 more bytes of GCE.

    w.WriteByte(byte(0x00)) // There is no transparent pixel.
    w.WriteByte(byte(0x10)) // Animation delay, in centiseconds, LSB.
    w.WriteByte(byte(0x00)) // Animation delay, in centiseconds, MSB.
    w.WriteByte(byte(0x00)) // Transparent color #, if we were using.
    w.WriteByte(byte(0x00)) // End of Application Extension data.

    w.WriteByte(byte(0x2C)) // Start of Image Descriptor.

    b := m.Bounds()
    w.WriteByte(byte(b.Min.X / 255))
    w.WriteByte(byte(b.Min.X % 255))
    w.WriteByte(byte(b.Min.Y / 255))
    w.WriteByte(byte(b.Min.Y % 255))

    w.WriteByte(byte(b.Max.X / 255))
    w.WriteByte(byte(b.Max.X % 255))
    w.WriteByte(byte(b.Max.Y / 255))
    w.WriteByte(byte(b.Max.Y % 255))

    w.WriteByte(byte(0x00)) // No local color table.
}

func compressImage(m image.Image) *bytes.Buffer {
    b := m.Bounds()

    compressedImageBuffer := bytes.NewBuffer(make([]byte, 0, 255))
    lzww := lzw.NewWriter(compressedImageBuffer, lzw.LSB, int(8))

    for y := b.Min.Y; y < b.Max.Y; y++ {
        for x := b.Min.X; x < b.Max.X; x++ {
            c := color.GrayModel.Convert(m.At(x, y)).(color.Gray)
            lzww.Write([]byte{c.Y})
        }
    }
    lzww.Close()

    return compressedImageBuffer
}

func writeFrame(w *bufio.Writer, m image.Image) {
    writeFrameHeader(w, m)

    w.WriteByte(byte(0x08)) // Start of LZW with minimum code size 8.

    compressedImage := compressImage(m)

    const maxBlockSize = 255
    bytesSoFar := 0
    bytesRemaining := compressedImage.Len()
    for bytesRemaining > 0 {
        if bytesSoFar == 0 {
            blockSize := math.Min(maxBlockSize, float64(bytesRemaining))
            w.WriteByte(byte(blockSize))
        }

        b, _ :=  compressedImage.ReadByte()
        w.WriteByte(b)

        bytesSoFar = (bytesSoFar + 1) % maxBlockSize
        bytesRemaining--
    }

    w.WriteByte(byte(0x00)) // End of LZW data.
}

func Encode(w io.Writer, m image.Image) error {
    return EncodeAll(w, []image.Image{m})
}

func EncodeAll(w io.Writer, images []image.Image) error {
    buffer := bufio.NewWriter(w)

    writeHeader(buffer, images[0])
    for _, m := range images {
        writeFrame(buffer, m)
    }
    buffer.WriteByte(';')
    buffer.Flush()

    return nil
}

func main() {
    m := image.NewRGBA(image.Rect(0, 0, 100, 100))
    for x := 0; x < 100; x++ {
        for y := 0; y < 100; y++ {
            c := byte(x ^ y)
            m.Set(x, y, color.RGBA{c, c, c, 0xFF})
        }
    }
    file, _ := os.Create("new_image.gif")
    Encode(file, m)
}
<commit_msg>Fix header bytes<commit_after>package main

import (
    //"fmt"
    "image"
    "image/color"
    //"image/gif"
    "os"
    "bufio"
    "io"
    "compress/lzw"
    "bytes"
    "math"
)

func writeHeader(w *bufio.Writer, m image.Image) {
    w.WriteByte('G')
    w.WriteByte('I')
    w.WriteByte('F')
    w.WriteByte('8')
    w.WriteByte('9')
    w.WriteByte('a')

    b := m.Bounds()
    w.WriteByte(byte(b.Max.X / 255)) // Image width, LSB.
    w.WriteByte(byte(b.Max.X % 255)) // Image width, MSB.
    w.WriteByte(byte(b.Max.Y / 255)) // Image height, LSB.
    w.WriteByte(byte(b.Max.Y % 255)) // Image height, MSB.

    w.WriteByte(byte(0xF7)) // GCT follows for 256 colors with resolution
                                 // 3 x 8 bits/primary
    w.WriteByte(byte(0x00)) // Background color.
    w.WriteByte(byte(0x00)) // Default pixel aspect ratio.

    // Grayscale color table.
    for i := 0; i < 256; i++ {
        w.WriteByte(byte(i)) // Repeat the same color value to get
        w.WriteByte(byte(i)) // grayscale.
        w.WriteByte(byte(i))
    }

    w.WriteByte(byte(0x21)) // Application Extension block.
    w.WriteByte(byte(0xFF)) // Application Extension block (cont).
    w.WriteByte(byte(0x0B)) // Next 11 bytes are Application Extension.
    w.WriteByte('N') // 8 Character application name.
    w.WriteByte('E')
    w.WriteByte('T')
    w.WriteByte('S')
    w.WriteByte('C')
    w.WriteByte('A')
    w.WriteByte('P')
    w.WriteByte('E')
    w.WriteByte('2')
    w.WriteByte('.')
    w.WriteByte('0')
    w.WriteByte(byte(0x03)) // 3 more bytes of Application Extension.
    w.WriteByte(byte(0x01)) // Data sub-block index (always 1).
    w.WriteByte(byte(0xFF)) // Number of repetitions, LSB.
    w.WriteByte(byte(0xFF)) // Number of repetitions, MSB.
    w.WriteByte(byte(0x00)) // End of Application Extension block.
}

func writeFrameHeader(w *bufio.Writer, m image.Image) {
    w.WriteByte(byte(0x21)) // Start of Graphic Control Extension.
    w.WriteByte(byte(0xF9)) // Start of Graphic Control Extension (cont).
    w.WriteByte(byte(0x04)) // 4 more bytes of GCE.

    w.WriteByte(byte(0x08)) // There is no transparent pixel.
    w.WriteByte(byte(0x10)) // Animation delay, in centiseconds, LSB.
    w.WriteByte(byte(0x00)) // Animation delay, in centiseconds, MSB.
    w.WriteByte(byte(0x00)) // Transparent color #, if we were using.
    w.WriteByte(byte(0x00)) // End of Application Extension data.

    w.WriteByte(byte(0x2C)) // Start of Image Descriptor.

    b := m.Bounds()
    w.WriteByte(byte(b.Min.X / 255))
    w.WriteByte(byte(b.Min.X % 255))
    w.WriteByte(byte(b.Min.Y / 255))
    w.WriteByte(byte(b.Min.Y % 255))

    w.WriteByte(byte(b.Max.X / 255))
    w.WriteByte(byte(b.Max.X % 255))
    w.WriteByte(byte(b.Max.Y / 255))
    w.WriteByte(byte(b.Max.Y % 255))

    w.WriteByte(byte(0x00)) // No local color table.
}

func compressImage(m image.Image) *bytes.Buffer {
    b := m.Bounds()

    compressedImageBuffer := bytes.NewBuffer(make([]byte, 0, 255))
    lzww := lzw.NewWriter(compressedImageBuffer, lzw.LSB, int(8))

    for y := b.Min.Y; y < b.Max.Y; y++ {
        for x := b.Min.X; x < b.Max.X; x++ {
            c := color.GrayModel.Convert(m.At(x, y)).(color.Gray)
            lzww.Write([]byte{c.Y})
        }
    }
    lzww.Close()

    return compressedImageBuffer
}

func writeFrame(w *bufio.Writer, m image.Image) {
    writeFrameHeader(w, m)

    w.WriteByte(byte(0x08)) // Start of LZW with minimum code size 8.

    compressedImage := compressImage(m)

    const maxBlockSize = 255
    bytesSoFar := 0
    bytesRemaining := compressedImage.Len()
    for bytesRemaining > 0 {
        if bytesSoFar == 0 {
            blockSize := math.Min(maxBlockSize, float64(bytesRemaining))
            w.WriteByte(byte(blockSize))
        }

        b, _ :=  compressedImage.ReadByte()
        w.WriteByte(b)

        bytesSoFar = (bytesSoFar + 1) % maxBlockSize
        bytesRemaining--
    }

    w.WriteByte(byte(0x00)) // End of LZW data.
}

func Encode(w io.Writer, m image.Image) error {
    return EncodeAll(w, []image.Image{m})
}

func EncodeAll(w io.Writer, images []image.Image) error {
    buffer := bufio.NewWriter(w)

    writeHeader(buffer, images[0])
    for _, m := range images {
        writeFrame(buffer, m)
    }
    buffer.WriteByte(';')
    buffer.Flush()

    return nil
}

func main() {
    m := image.NewRGBA(image.Rect(0, 0, 100, 100))
    for x := 0; x < 100; x++ {
        for y := 0; y < 100; y++ {
            c := byte(x ^ y)
            m.Set(x, y, color.RGBA{c, c, c, 0xFF})
        }
    }
    file, _ := os.Create("new_image.gif")
    Encode(file, m)
}
<|endoftext|>


<commit_before>// Copyright 2014 The Serviced Authors.
// 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 isvcs

var logstash *IService

func initLogstash() {
	var err error

	command := "exec /opt/logstash/bin/logstash agent -f /usr/local/serviced/resources/logstash/logstash.conf"
	localFilePortBinding := portBinding{
		HostIp:         "0.0.0.0",
		HostIpOverride: "", // logstash should always be open
		HostPort:       5042,
	}
	filebeatPortBinding := portBinding{
		HostIp:         "0.0.0.0",
		HostIpOverride: "", // filebeat should always be open
		HostPort:       5043,
	}
	webserverPortBinding := portBinding{
		HostIp:         "127.0.0.1",
		HostIpOverride: "SERVICED_ISVC_LOGSTASH_PORT_9292_HOSTIP",
		HostPort:       9292,
	}

	logstash, err = NewIService(
		IServiceDefinition{
			ID:      LogstashISVC.ID,
			Name:    "logstash",
			Repo:    IMAGE_REPO,
			Tag:     IMAGE_TAG,
			Command: func() string { return command },
			PortBindings: []portBinding{
				localFilePortBinding,
				filebeatPortBinding,
				webserverPortBinding},
			Volumes:    map[string]string{},
			Notify:     notifyLogstashConfigChange,
			Links:      []string{"serviced-isvcs_elasticsearch-logstash:elasticsearch"},
			StartGroup: 1,
		})
	if err != nil {
		log.WithError(err).Fatal("Unable to initialize Logstash internal service container")
	}
}

func notifyLogstashConfigChange(svc *IService, value interface{}) error {

	if message, ok := value.(string); ok {
		if message == "restart logstash" {
			return svc.Restart()
		}
	}
	return nil
}
<commit_msg>Auto reload logstash configuration<commit_after>// Copyright 2014 The Serviced Authors.
// 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 isvcs

var logstash *IService

func initLogstash() {
	var err error

	command := "exec /opt/logstash/bin/logstash agent " +
		"-f /usr/local/serviced/resources/logstash/logstash.conf --auto-reload"
	localFilePortBinding := portBinding{
		HostIp:         "0.0.0.0",
		HostIpOverride: "", // logstash should always be open
		HostPort:       5042,
	}
	filebeatPortBinding := portBinding{
		HostIp:         "0.0.0.0",
		HostIpOverride: "", // filebeat should always be open
		HostPort:       5043,
	}
	webserverPortBinding := portBinding{
		HostIp:         "127.0.0.1",
		HostIpOverride: "SERVICED_ISVC_LOGSTASH_PORT_9292_HOSTIP",
		HostPort:       9292,
	}

	logstash, err = NewIService(
		IServiceDefinition{
			ID:      LogstashISVC.ID,
			Name:    "logstash",
			Repo:    IMAGE_REPO,
			Tag:     IMAGE_TAG,
			Command: func() string { return command },
			PortBindings: []portBinding{
				localFilePortBinding,
				filebeatPortBinding,
				webserverPortBinding},
			Volumes:    map[string]string{},
			Notify:     notifyLogstashConfigChange,
			Links:      []string{"serviced-isvcs_elasticsearch-logstash:elasticsearch"},
			StartGroup: 1,
		})
	if err != nil {
		log.WithError(err).Fatal("Unable to initialize Logstash internal service container")
	}
}

func notifyLogstashConfigChange(svc *IService, value interface{}) error {

	if message, ok := value.(string); ok {
		if message == "restart logstash" {
			return svc.Restart()
		}
	}
	return nil
}
<|endoftext|>


<commit_before>package arn

import "sort"

// GetUser ...
func GetUser(id string) (*User, error) {
	obj, err := DB.Get("User", id)
	return obj.(*User), err
}

// GetUserByNick ...
func GetUserByNick(nick string) (*User, error) {
	return GetUserFromTable("NickToUser", nick)
}

// GetUserByEmail ...
func GetUserByEmail(email string) (*User, error) {
	return GetUserFromTable("EmailToUser", email)
}

// GetUserFromTable queries a table for the record with the given ID
// and returns the user that is referenced by record["userId"].
func GetUserFromTable(table string, id string) (*User, error) {
	rec, err := DB.GetMap(table, id)

	if err != nil {
		return nil, err
	}

	return GetUser(rec["userId"].(string))
}

// AllUsers returns a stream of all users.
func AllUsers() (chan *User, error) {
	channel := make(chan *User)
	err := DB.Scan("User", channel)
	return channel, err
}

// FilterUsers filters all users by a custom function.
func FilterUsers(filter func(*User) bool) ([]*User, error) {
	var filtered []*User

	channel, err := AllUsers()

	if err != nil {
		return filtered, err
	}

	for obj := range channel {
		if filter(obj) {
			filtered = append(filtered, obj)
		}
	}

	return filtered, nil
}

// SortUserByLastSeen sorts a list of users by their last seen date.
func SortByLastSeen(users []*User) []*User {

	sort.Slice(users, func(i, j int) bool {
		return users[i].LastSeen > users[j].LastSeen
	})

	return users
}
<commit_msg>updated function name<commit_after>package arn

import "sort"

// GetUser ...
func GetUser(id string) (*User, error) {
	obj, err := DB.Get("User", id)
	return obj.(*User), err
}

// GetUserByNick ...
func GetUserByNick(nick string) (*User, error) {
	return GetUserFromTable("NickToUser", nick)
}

// GetUserByEmail ...
func GetUserByEmail(email string) (*User, error) {
	return GetUserFromTable("EmailToUser", email)
}

// GetUserFromTable queries a table for the record with the given ID
// and returns the user that is referenced by record["userId"].
func GetUserFromTable(table string, id string) (*User, error) {
	rec, err := DB.GetMap(table, id)

	if err != nil {
		return nil, err
	}

	return GetUser(rec["userId"].(string))
}

// AllUsers returns a stream of all users.
func AllUsers() (chan *User, error) {
	channel := make(chan *User)
	err := DB.Scan("User", channel)
	return channel, err
}

// FilterUsers filters all users by a custom function.
func FilterUsers(filter func(*User) bool) ([]*User, error) {
	var filtered []*User

	channel, err := AllUsers()

	if err != nil {
		return filtered, err
	}

	for obj := range channel {
		if filter(obj) {
			filtered = append(filtered, obj)
		}
	}

	return filtered, nil
}

// SortUsersLastSeen sorts a list of users by their last seen date.
func SortUsersLastSeen(users []*User) []*User {

	sort.Slice(users, func(i, j int) bool {
		return users[i].LastSeen > users[j].LastSeen
	})

	return users
}
<|endoftext|>


<commit_before>// Copyright 2012 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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 s3

import (
	"bytes"
	"crypto/md5"
	"encoding/base64"
	"encoding/xml"
	"fmt"
	"github.com/jacobsa/aws"
	"github.com/jacobsa/aws/s3/auth"
	"github.com/jacobsa/aws/s3/http"
	"github.com/jacobsa/aws/s3/time"
	"net/url"
	sys_time "time"
	"unicode/utf8"
)

// Bucket represents an S3 bucket, which is a collection of objects keyed on
// Unicode strings.
//
// Keys must be non-empty sequences of Unicode characters whose UTF-8 encoding
// is no more than 1024 bytes long. Because S3 returns "list bucket" responses
// as XML 1.0 documents, keys must contain no character that is not a legal
// character as defined by Section 2.2 of the XML 1.0 spec.
//
// See here for more info:
//
//     http://goo.gl/Nd63t
//     http://goo.gl/csem8
//
type Bucket interface {
	// Retrieve data for the object with the given key.
	GetObject(key string) (data []byte, err error)

	// Store the supplied data with the given key, overwriting any previous
	// version. The object is created with the default ACL of "private".
	StoreObject(key string, data []byte) error

	// Delete the object with the supplied key.
	DeleteObject(key string) error

	// Return an ordered set of contiguous object keys in the bucket that are
	// strictly greater than prevKey (or at the beginning of the range if prevKey
	// is empty). It is guaranteed that as some time during the request there
	// were no keys greater than prevKey and less than the first key returned.
	//
	// There may be more keys beyond the last key returned. If no keys are
	// returned (and the error is nil), it is guaranteed that at some time during
	// the request there were the bucket contained no keys in (prevKey, inf).
	//
	// Using this interface you may list all keys in a bucket by starting with
	// the empty string for prevKey (since the empty string is not itself a legal
	// key) and then repeatedly calling again with the last key returned by the
	// previous call.
	//
	// prevKey must be a valid key, with the sole exception that it is allowed to
	// be the empty string.
	ListKeys(prevKey string) (keys []string, err error)
}

// OpenBucket returns a Bucket tied to a given name in whe given region. You
// must have previously created the bucket in the region, and the supplied
// access key must have access to it.
//
// To easily create a bucket, use the AWS Console:
//
//     https://console.aws.amazon.com/s3/
//
func OpenBucket(name string, region Region, key aws.AccessKey) (Bucket, error) {
	// Create a connection to the given region's endpoint.
	endpoint := &url.URL{Scheme: "https", Host: string(region)}
	httpConn, err := http.NewConn(endpoint)
	if err != nil {
		return nil, fmt.Errorf("http.NewConn: %v", err)
	}

	// Create an appropriate request signer.
	signer, err := auth.NewSigner(&key)
	if err != nil {
		return nil, fmt.Errorf("auth.NewSigner: %v", err)
	}

	return openBucket(name, httpConn, signer, time.RealClock())
}

// A version of OpenBucket with the ability to inject dependencies, for
// testability.
func openBucket(
	name string,
	httpConn http.Conn,
	signer auth.Signer,
	clock time.Clock) (Bucket, error) {
	return &bucket{name, httpConn, signer, clock}, nil
}

type bucket struct {
	name     string
	httpConn http.Conn
	signer   auth.Signer
	clock    time.Clock
}

////////////////////////////////////////////////////////////////////////
// Common
////////////////////////////////////////////////////////////////////////

func isLegalXmlCharacter(r rune) (inrange bool) {
	return r == 0x09 ||
		r == 0x0A ||
		r == 0x0D ||
		(r >= 0x20 && r <= 0xDF77) ||
		(r >= 0xE000 && r <= 0xFFFD) ||
		(r >= 0x10000 && r <= 0x10FFFF)
}

func validateKey(key string) error {
	// Keys must be valid UTF-8 and no more than 1024 bytes long.
	if len(key) > 1024 {
		return fmt.Errorf("Keys may be no longer than 1024 bytes.")
	}

	if !utf8.ValidString(key) {
		return fmt.Errorf("Keys must be valid UTF-8.")
	}

	// Because of the semantics of the "LIST bucket" request, keys must be
	// non-empty. (Otherwise an empty marker would exclude the first key.) Amazon
	// will reject empty keys with an HTTP 400 response.
	if key == "" {
		return fmt.Errorf("Keys must be non-empty.")
	}

	// Because "LIST bucket" responses are expressed using XML 1.0, keys must
	// contain only characters valid in XML 1.0.
	for _, r := range key {
		if !isLegalXmlCharacter(r) {
			return fmt.Errorf("Key contains invalid codepoint: %U", r)
		}
	}

	return nil
}

func addMd5Header(r *http.Request, body []byte) error {
	md5Hash := md5.New()
	if _, err := md5Hash.Write(body); err != nil {
		return fmt.Errorf("md5Hash.Write: %v", err)
	}

	base64Md5Buf := new(bytes.Buffer)
	base64Encoder := base64.NewEncoder(base64.StdEncoding, base64Md5Buf)
	if _, err := base64Encoder.Write(md5Hash.Sum(nil)); err != nil {
		return fmt.Errorf("base64Encoder.Write: %v", err)
	}

	base64Encoder.Close()
	r.Headers["Content-MD5"] = base64Md5Buf.String()

	return nil
}

////////////////////////////////////////////////////////////////////////
// GetObject
////////////////////////////////////////////////////////////////////////

func (b *bucket) GetObject(key string) (data []byte, err error) {
	// Validate the key.
	if err := validateKey(key); err != nil {
		return nil, err
	}

	// Build an appropriate HTTP request.
	//
	// Reference:
	//     http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTObjectGET.html
	httpReq := &http.Request{
		Verb: "GET",
		Path: fmt.Sprintf("/%s/%s", b.name, key),
		Headers: map[string]string{
			"Date": b.clock.Now().UTC().Format(sys_time.RFC1123),
		},
	}

	// Sign the request.
	if err := b.signer.Sign(httpReq); err != nil {
		return nil, fmt.Errorf("Sign: %v", err)
	}

	// Send the request.
	httpResp, err := b.httpConn.SendRequest(httpReq)
	if err != nil {
		return nil, fmt.Errorf("SendRequest: %v", err)
	}

	// Check the response.
	if httpResp.StatusCode != 200 {
		return nil, fmt.Errorf("Error from server: %d %s", httpResp.StatusCode, httpResp.Body)
	}

	return httpResp.Body, nil
}

////////////////////////////////////////////////////////////////////////
// StoreObject
////////////////////////////////////////////////////////////////////////

func (b *bucket) StoreObject(key string, data []byte) error {
	// Validate the key.
	if err := validateKey(key); err != nil {
		return err
	}

	// Build an appropriate HTTP request.
	//
	// Reference:
	//     http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTObjectPUT.html
	httpReq := &http.Request{
		Verb: "PUT",
		Path: fmt.Sprintf("/%s/%s", b.name, key),
		Body: data,
		Headers: map[string]string{
			"Date": b.clock.Now().UTC().Format(sys_time.RFC1123),
		},
	}

	// Add a Content-MD5 header, as advised in the Amazon docs.
	if err := addMd5Header(httpReq, httpReq.Body); err != nil {
		return err
	}

	// Sign the request.
	if err := b.signer.Sign(httpReq); err != nil {
		return fmt.Errorf("Sign: %v", err)
	}

	// Send the request.
	httpResp, err := b.httpConn.SendRequest(httpReq)
	if err != nil {
		return fmt.Errorf("SendRequest: %v", err)
	}

	// Check the response.
	if httpResp.StatusCode != 200 {
		return fmt.Errorf("Error from server: %d %s", httpResp.StatusCode, httpResp.Body)
	}

	return nil
}

////////////////////////////////////////////////////////////////////////
// DeleteObject
////////////////////////////////////////////////////////////////////////

func (b *bucket) DeleteObject(key string) error {
	// Validate the key.
	if err := validateKey(key); err != nil {
		return err
	}

	// Build an appropriate HTTP request.
	//
	// Reference:
	//     http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTObjectDELETE.html
	httpReq := &http.Request{
		Verb: "DELETE",
		Path: fmt.Sprintf("/%s/%s", b.name, key),
		Headers: map[string]string{
			"Date": b.clock.Now().UTC().Format(sys_time.RFC1123),
		},
	}

	// Add a Content-MD5 header, as advised in the Amazon docs.
	if err := addMd5Header(httpReq, httpReq.Body); err != nil {
		return err
	}

	// Sign the request.
	if err := b.signer.Sign(httpReq); err != nil {
		return fmt.Errorf("Sign: %v", err)
	}

	// Send the request.
	httpResp, err := b.httpConn.SendRequest(httpReq)
	if err != nil {
		return fmt.Errorf("SendRequest: %v", err)
	}

	// Check the response.
	if httpResp.StatusCode != 204 {
		return fmt.Errorf("Error from server: %d %s", httpResp.StatusCode, httpResp.Body)
	}

	return nil
}

////////////////////////////////////////////////////////////////////////
// ListKeys
////////////////////////////////////////////////////////////////////////

type bucketContents struct {
	Key string
}

type listBucketResult struct {
	XMLName  xml.Name
	Contents []bucketContents
}

func (b *bucket) ListKeys(prevKey string) (keys []string, err error) {
	// Build an appropriate HTTP request.
	//
	// Reference:
	//     http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTBucketGET.html
	httpReq := &http.Request{
		Verb: "GET",
		Path: fmt.Sprintf("/%s", b.name),
		Headers: map[string]string{
			"Date": b.clock.Now().UTC().Format(sys_time.RFC1123),
		},
		Parameters: map[string]string{},
	}

	if prevKey != "" {
		httpReq.Parameters["marker"] = prevKey
	}

	// Sign the request.
	if err := b.signer.Sign(httpReq); err != nil {
		return nil, fmt.Errorf("Sign: %v", err)
	}

	// Send the request.
	httpResp, err := b.httpConn.SendRequest(httpReq)
	if err != nil {
		return nil, fmt.Errorf("SendRequest: %v", err)
	}

	// Check the response.
	if httpResp.StatusCode != 200 {
		return nil, fmt.Errorf("Error from server: %d %s", httpResp.StatusCode, httpResp.Body)
	}

	// Attempt to parse the body.
	result := listBucketResult{}
	if err := xml.Unmarshal(httpResp.Body, &result); err != nil {
		return nil, fmt.Errorf(
			"Invalid data from server (%s): %s",
			err.Error(),
			httpResp.Body)
	}

	// Make sure the server agress with us about the interpretation of the
	// request.
	if result.XMLName.Local != "ListBucketResult" {
		return nil, fmt.Errorf("Invalid data from server: %s", httpResp.Body)
	}

	keys = make([]string, len(result.Contents))
	for i, elem := range result.Contents {
		keys[i] = elem.Key
	}

	return keys, nil
}
<commit_msg>Added validation for ListKeys.<commit_after>// Copyright 2012 Aaron Jacobs. All Rights Reserved.
// Author: aaronjjacobs@gmail.com (Aaron Jacobs)
//
// 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 s3

import (
	"bytes"
	"crypto/md5"
	"encoding/base64"
	"encoding/xml"
	"fmt"
	"github.com/jacobsa/aws"
	"github.com/jacobsa/aws/s3/auth"
	"github.com/jacobsa/aws/s3/http"
	"github.com/jacobsa/aws/s3/time"
	"net/url"
	sys_time "time"
	"unicode/utf8"
)

// Bucket represents an S3 bucket, which is a collection of objects keyed on
// Unicode strings.
//
// Keys must be non-empty sequences of Unicode characters whose UTF-8 encoding
// is no more than 1024 bytes long. Because S3 returns "list bucket" responses
// as XML 1.0 documents, keys must contain no character that is not a legal
// character as defined by Section 2.2 of the XML 1.0 spec.
//
// See here for more info:
//
//     http://goo.gl/Nd63t
//     http://goo.gl/csem8
//
type Bucket interface {
	// Retrieve data for the object with the given key.
	GetObject(key string) (data []byte, err error)

	// Store the supplied data with the given key, overwriting any previous
	// version. The object is created with the default ACL of "private".
	StoreObject(key string, data []byte) error

	// Delete the object with the supplied key.
	DeleteObject(key string) error

	// Return an ordered set of contiguous object keys in the bucket that are
	// strictly greater than prevKey (or at the beginning of the range if prevKey
	// is empty). It is guaranteed that as some time during the request there
	// were no keys greater than prevKey and less than the first key returned.
	//
	// There may be more keys beyond the last key returned. If no keys are
	// returned (and the error is nil), it is guaranteed that at some time during
	// the request there were the bucket contained no keys in (prevKey, inf).
	//
	// Using this interface you may list all keys in a bucket by starting with
	// the empty string for prevKey (since the empty string is not itself a legal
	// key) and then repeatedly calling again with the last key returned by the
	// previous call.
	//
	// prevKey must be a valid key, with the sole exception that it is allowed to
	// be the empty string.
	ListKeys(prevKey string) (keys []string, err error)
}

// OpenBucket returns a Bucket tied to a given name in whe given region. You
// must have previously created the bucket in the region, and the supplied
// access key must have access to it.
//
// To easily create a bucket, use the AWS Console:
//
//     https://console.aws.amazon.com/s3/
//
func OpenBucket(name string, region Region, key aws.AccessKey) (Bucket, error) {
	// Create a connection to the given region's endpoint.
	endpoint := &url.URL{Scheme: "https", Host: string(region)}
	httpConn, err := http.NewConn(endpoint)
	if err != nil {
		return nil, fmt.Errorf("http.NewConn: %v", err)
	}

	// Create an appropriate request signer.
	signer, err := auth.NewSigner(&key)
	if err != nil {
		return nil, fmt.Errorf("auth.NewSigner: %v", err)
	}

	return openBucket(name, httpConn, signer, time.RealClock())
}

// A version of OpenBucket with the ability to inject dependencies, for
// testability.
func openBucket(
	name string,
	httpConn http.Conn,
	signer auth.Signer,
	clock time.Clock) (Bucket, error) {
	return &bucket{name, httpConn, signer, clock}, nil
}

type bucket struct {
	name     string
	httpConn http.Conn
	signer   auth.Signer
	clock    time.Clock
}

////////////////////////////////////////////////////////////////////////
// Common
////////////////////////////////////////////////////////////////////////

func isLegalXmlCharacter(r rune) (inrange bool) {
	return r == 0x09 ||
		r == 0x0A ||
		r == 0x0D ||
		(r >= 0x20 && r <= 0xDF77) ||
		(r >= 0xE000 && r <= 0xFFFD) ||
		(r >= 0x10000 && r <= 0x10FFFF)
}

func validateKey(key string) error {
	// Keys must be valid UTF-8 and no more than 1024 bytes long.
	if len(key) > 1024 {
		return fmt.Errorf("Keys may be no longer than 1024 bytes.")
	}

	if !utf8.ValidString(key) {
		return fmt.Errorf("Keys must be valid UTF-8.")
	}

	// Because of the semantics of the "LIST bucket" request, keys must be
	// non-empty. (Otherwise an empty marker would exclude the first key.) Amazon
	// will reject empty keys with an HTTP 400 response.
	if key == "" {
		return fmt.Errorf("Keys must be non-empty.")
	}

	// Because "LIST bucket" responses are expressed using XML 1.0, keys must
	// contain only characters valid in XML 1.0.
	for _, r := range key {
		if !isLegalXmlCharacter(r) {
			return fmt.Errorf("Key contains invalid codepoint: %U", r)
		}
	}

	return nil
}

func addMd5Header(r *http.Request, body []byte) error {
	md5Hash := md5.New()
	if _, err := md5Hash.Write(body); err != nil {
		return fmt.Errorf("md5Hash.Write: %v", err)
	}

	base64Md5Buf := new(bytes.Buffer)
	base64Encoder := base64.NewEncoder(base64.StdEncoding, base64Md5Buf)
	if _, err := base64Encoder.Write(md5Hash.Sum(nil)); err != nil {
		return fmt.Errorf("base64Encoder.Write: %v", err)
	}

	base64Encoder.Close()
	r.Headers["Content-MD5"] = base64Md5Buf.String()

	return nil
}

////////////////////////////////////////////////////////////////////////
// GetObject
////////////////////////////////////////////////////////////////////////

func (b *bucket) GetObject(key string) (data []byte, err error) {
	// Validate the key.
	if err := validateKey(key); err != nil {
		return nil, err
	}

	// Build an appropriate HTTP request.
	//
	// Reference:
	//     http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTObjectGET.html
	httpReq := &http.Request{
		Verb: "GET",
		Path: fmt.Sprintf("/%s/%s", b.name, key),
		Headers: map[string]string{
			"Date": b.clock.Now().UTC().Format(sys_time.RFC1123),
		},
	}

	// Sign the request.
	if err := b.signer.Sign(httpReq); err != nil {
		return nil, fmt.Errorf("Sign: %v", err)
	}

	// Send the request.
	httpResp, err := b.httpConn.SendRequest(httpReq)
	if err != nil {
		return nil, fmt.Errorf("SendRequest: %v", err)
	}

	// Check the response.
	if httpResp.StatusCode != 200 {
		return nil, fmt.Errorf("Error from server: %d %s", httpResp.StatusCode, httpResp.Body)
	}

	return httpResp.Body, nil
}

////////////////////////////////////////////////////////////////////////
// StoreObject
////////////////////////////////////////////////////////////////////////

func (b *bucket) StoreObject(key string, data []byte) error {
	// Validate the key.
	if err := validateKey(key); err != nil {
		return err
	}

	// Build an appropriate HTTP request.
	//
	// Reference:
	//     http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTObjectPUT.html
	httpReq := &http.Request{
		Verb: "PUT",
		Path: fmt.Sprintf("/%s/%s", b.name, key),
		Body: data,
		Headers: map[string]string{
			"Date": b.clock.Now().UTC().Format(sys_time.RFC1123),
		},
	}

	// Add a Content-MD5 header, as advised in the Amazon docs.
	if err := addMd5Header(httpReq, httpReq.Body); err != nil {
		return err
	}

	// Sign the request.
	if err := b.signer.Sign(httpReq); err != nil {
		return fmt.Errorf("Sign: %v", err)
	}

	// Send the request.
	httpResp, err := b.httpConn.SendRequest(httpReq)
	if err != nil {
		return fmt.Errorf("SendRequest: %v", err)
	}

	// Check the response.
	if httpResp.StatusCode != 200 {
		return fmt.Errorf("Error from server: %d %s", httpResp.StatusCode, httpResp.Body)
	}

	return nil
}

////////////////////////////////////////////////////////////////////////
// DeleteObject
////////////////////////////////////////////////////////////////////////

func (b *bucket) DeleteObject(key string) error {
	// Validate the key.
	if err := validateKey(key); err != nil {
		return err
	}

	// Build an appropriate HTTP request.
	//
	// Reference:
	//     http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTObjectDELETE.html
	httpReq := &http.Request{
		Verb: "DELETE",
		Path: fmt.Sprintf("/%s/%s", b.name, key),
		Headers: map[string]string{
			"Date": b.clock.Now().UTC().Format(sys_time.RFC1123),
		},
	}

	// Add a Content-MD5 header, as advised in the Amazon docs.
	if err := addMd5Header(httpReq, httpReq.Body); err != nil {
		return err
	}

	// Sign the request.
	if err := b.signer.Sign(httpReq); err != nil {
		return fmt.Errorf("Sign: %v", err)
	}

	// Send the request.
	httpResp, err := b.httpConn.SendRequest(httpReq)
	if err != nil {
		return fmt.Errorf("SendRequest: %v", err)
	}

	// Check the response.
	if httpResp.StatusCode != 204 {
		return fmt.Errorf("Error from server: %d %s", httpResp.StatusCode, httpResp.Body)
	}

	return nil
}

////////////////////////////////////////////////////////////////////////
// ListKeys
////////////////////////////////////////////////////////////////////////

type bucketContents struct {
	Key string
}

type listBucketResult struct {
	XMLName  xml.Name
	Contents []bucketContents
}

func (b *bucket) ListKeys(prevKey string) (keys []string, err error) {
	// Make sure the previous key is empty or valid.
	if err := validateKey(prevKey); err != nil && prevKey != "" {
		return nil, err
	}

	// Build an appropriate HTTP request.
	//
	// Reference:
	//     http://docs.amazonwebservices.com/AmazonS3/latest/API/RESTBucketGET.html
	httpReq := &http.Request{
		Verb: "GET",
		Path: fmt.Sprintf("/%s", b.name),
		Headers: map[string]string{
			"Date": b.clock.Now().UTC().Format(sys_time.RFC1123),
		},
		Parameters: map[string]string{},
	}

	if prevKey != "" {
		httpReq.Parameters["marker"] = prevKey
	}

	// Sign the request.
	if err := b.signer.Sign(httpReq); err != nil {
		return nil, fmt.Errorf("Sign: %v", err)
	}

	// Send the request.
	httpResp, err := b.httpConn.SendRequest(httpReq)
	if err != nil {
		return nil, fmt.Errorf("SendRequest: %v", err)
	}

	// Check the response.
	if httpResp.StatusCode != 200 {
		return nil, fmt.Errorf("Error from server: %d %s", httpResp.StatusCode, httpResp.Body)
	}

	// Attempt to parse the body.
	result := listBucketResult{}
	if err := xml.Unmarshal(httpResp.Body, &result); err != nil {
		return nil, fmt.Errorf(
			"Invalid data from server (%s): %s",
			err.Error(),
			httpResp.Body)
	}

	// Make sure the server agress with us about the interpretation of the
	// request.
	if result.XMLName.Local != "ListBucketResult" {
		return nil, fmt.Errorf("Invalid data from server: %s", httpResp.Body)
	}

	keys = make([]string, len(result.Contents))
	for i, elem := range result.Contents {
		keys[i] = elem.Key
	}

	return keys, nil
}
<|endoftext|>