<commit_before>/*
   Copyright The containerd 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 containers

import (
	"context"
	"errors"
	"fmt"
	"os"
	"strings"
	"text/tabwriter"

	"github.com/containerd/containerd"
	"github.com/containerd/containerd/cio"
	"github.com/containerd/containerd/cmd/ctr/commands"
	"github.com/containerd/containerd/cmd/ctr/commands/run"
	"github.com/containerd/containerd/containers"
	"github.com/containerd/containerd/log"
	"github.com/containerd/typeurl"
	"github.com/urfave/cli"
)

// Command is the cli command for managing containers
var Command = cli.Command{
	Name:    "containers",
	Usage:   "manage containers",
	Aliases: []string{"c", "container"},
	Subcommands: []cli.Command{
		createCommand,
		deleteCommand,
		infoCommand,
		listCommand,
		setLabelsCommand,
	},
}

var createCommand = cli.Command{
	Name:      "create",
	Usage:     "create container",
	ArgsUsage: "[flags] Image|RootFS CONTAINER",
	Flags:     append(commands.SnapshotterFlags, commands.ContainerFlags...),
	Action: func(context *cli.Context) error {
		var (
			id     string
			ref    string
			config = context.IsSet("config")
		)

		if config {
			id = context.Args().First()
			if context.NArg() > 1 {
				return errors.New("with spec config file, only container id should be provided")
			}
		} else {
			id = context.Args().Get(1)
			ref = context.Args().First()
			if ref == "" {
				return errors.New("image ref must be provided")
			}
		}
		if id == "" {
			return errors.New("container id must be provided")
		}
		client, ctx, cancel, err := commands.NewClient(context)
		if err != nil {
			return err
		}
		defer cancel()
		_, err = run.NewContainer(ctx, client, context)
		if err != nil {
			return err
		}
		return nil
	},
}

var listCommand = cli.Command{
	Name:      "list",
	Aliases:   []string{"ls"},
	Usage:     "list containers",
	ArgsUsage: "[flags] [<filter>, ...]",
	Flags: []cli.Flag{
		cli.BoolFlag{
			Name:  "quiet, q",
			Usage: "print only the container id",
		},
	},
	Action: func(context *cli.Context) error {
		var (
			filters = context.Args()
			quiet   = context.Bool("quiet")
		)
		client, ctx, cancel, err := commands.NewClient(context)
		if err != nil {
			return err
		}
		defer cancel()
		containers, err := client.Containers(ctx, filters...)
		if err != nil {
			return err
		}
		if quiet {
			for _, c := range containers {
				fmt.Printf("%s\n", c.ID())
			}
			return nil
		}
		w := tabwriter.NewWriter(os.Stdout, 4, 8, 4, ' ', 0)
		fmt.Fprintln(w, "CONTAINER\tIMAGE\tRUNTIME\t")
		for _, c := range containers {
			info, err := c.Info(ctx)
			if err != nil {
				return err
			}
			imageName := info.Image
			if imageName == "" {
				imageName = "-"
			}
			if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t\n",
				c.ID(),
				imageName,
				info.Runtime.Name,
			); err != nil {
				return err
			}
		}
		return w.Flush()
	},
}

var deleteCommand = cli.Command{
	Name:      "delete",
	Usage:     "delete one or more existing containers",
	ArgsUsage: "[flags] CONTAINER [CONTAINER, ...]",
	Aliases:   []string{"del", "rm"},
	Flags: []cli.Flag{
		cli.BoolFlag{
			Name:  "keep-snapshot",
			Usage: "do not clean up snapshot with container",
		},
	},
	Action: func(context *cli.Context) error {
		var exitErr error
		client, ctx, cancel, err := commands.NewClient(context)
		if err != nil {
			return err
		}
		defer cancel()
		deleteOpts := []containerd.DeleteOpts{}
		if !context.Bool("keep-snapshot") {
			deleteOpts = append(deleteOpts, containerd.WithSnapshotCleanup)
		}

		if context.NArg() == 0 {
			return errors.New("must specify at least one container to delete")
		}
		for _, arg := range context.Args() {
			if err := deleteContainer(ctx, client, arg, deleteOpts...); err != nil {
				if exitErr == nil {
					exitErr = err
				}
				log.G(ctx).WithError(err).Errorf("failed to delete container %q", arg)
			}
		}
		return exitErr
	},
}

func deleteContainer(ctx context.Context, client *containerd.Client, id string, opts ...containerd.DeleteOpts) error {
	container, err := client.LoadContainer(ctx, id)
	if err != nil {
		return err
	}
	task, err := container.Task(ctx, cio.Load)
	if err != nil {
		return container.Delete(ctx, opts...)
	}
	status, err := task.Status(ctx)
	if err != nil {
		return err
	}
	if status.Status == containerd.Stopped || status.Status == containerd.Created {
		if _, err := task.Delete(ctx); err != nil {
			return err
		}
		return container.Delete(ctx, opts...)
	}
	return fmt.Errorf("cannot delete a non stopped container: %v", status)

}

var setLabelsCommand = cli.Command{
	Name:        "label",
	Usage:       "set and clear labels for a container",
	ArgsUsage:   "[flags] CONTAINER [<key>=<value>, ...]",
	Description: "set and clear labels for a container",
	Flags:       []cli.Flag{},
	Action: func(context *cli.Context) error {
		containerID, labels := commands.ObjectWithLabelArgs(context)
		if containerID == "" {
			return errors.New("container id must be provided")
		}
		client, ctx, cancel, err := commands.NewClient(context)
		if err != nil {
			return err
		}
		defer cancel()

		container, err := client.LoadContainer(ctx, containerID)
		if err != nil {
			return err
		}

		setlabels, err := container.SetLabels(ctx, labels)
		if err != nil {
			return err
		}

		var labelStrings []string
		for k, v := range setlabels {
			labelStrings = append(labelStrings, fmt.Sprintf("%s=%s", k, v))
		}

		fmt.Println(strings.Join(labelStrings, ","))

		return nil
	},
}

var infoCommand = cli.Command{
	Name:      "info",
	Usage:     "get info about a container",
	ArgsUsage: "CONTAINER",
	Action: func(context *cli.Context) error {
		id := context.Args().First()
		if id == "" {
			return errors.New("container id must be provided")
		}
		client, ctx, cancel, err := commands.NewClient(context)
		if err != nil {
			return err
		}
		defer cancel()
		container, err := client.LoadContainer(ctx, id)
		if err != nil {
			return err
		}
		info, err := container.Info(ctx)
		if err != nil {
			return err
		}

		if info.Spec != nil && info.Spec.Value != nil {
			v, err := typeurl.UnmarshalAny(info.Spec)
			if err != nil {
				return err
			}
			commands.PrintAsJSON(struct {
				containers.Container
				Spec interface{} `json:"Spec,omitempty"`
			}{
				Container: info,
				Spec:      v,
			})
			return nil
		}
		commands.PrintAsJSON(info)
		return nil
	},
}
<commit_msg>change args usage for ctr c create<commit_after>/*
   Copyright The containerd 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 containers

import (
	"context"
	"errors"
	"fmt"
	"os"
	"strings"
	"text/tabwriter"

	"github.com/containerd/containerd"
	"github.com/containerd/containerd/cio"
	"github.com/containerd/containerd/cmd/ctr/commands"
	"github.com/containerd/containerd/cmd/ctr/commands/run"
	"github.com/containerd/containerd/containers"
	"github.com/containerd/containerd/log"
	"github.com/containerd/typeurl"
	"github.com/urfave/cli"
)

// Command is the cli command for managing containers
var Command = cli.Command{
	Name:    "containers",
	Usage:   "manage containers",
	Aliases: []string{"c", "container"},
	Subcommands: []cli.Command{
		createCommand,
		deleteCommand,
		infoCommand,
		listCommand,
		setLabelsCommand,
	},
}

var createCommand = cli.Command{
	Name:      "create",
	Usage:     "create container",
	ArgsUsage: "[flags] Image|RootFS CONTAINER [COMMAND] [ARG...]",
	Flags:     append(commands.SnapshotterFlags, commands.ContainerFlags...),
	Action: func(context *cli.Context) error {
		var (
			id     string
			ref    string
			config = context.IsSet("config")
		)

		if config {
			id = context.Args().First()
			if context.NArg() > 1 {
				return errors.New("with spec config file, only container id should be provided")
			}
		} else {
			id = context.Args().Get(1)
			ref = context.Args().First()
			if ref == "" {
				return errors.New("image ref must be provided")
			}
		}
		if id == "" {
			return errors.New("container id must be provided")
		}
		client, ctx, cancel, err := commands.NewClient(context)
		if err != nil {
			return err
		}
		defer cancel()
		_, err = run.NewContainer(ctx, client, context)
		if err != nil {
			return err
		}
		return nil
	},
}

var listCommand = cli.Command{
	Name:      "list",
	Aliases:   []string{"ls"},
	Usage:     "list containers",
	ArgsUsage: "[flags] [<filter>, ...]",
	Flags: []cli.Flag{
		cli.BoolFlag{
			Name:  "quiet, q",
			Usage: "print only the container id",
		},
	},
	Action: func(context *cli.Context) error {
		var (
			filters = context.Args()
			quiet   = context.Bool("quiet")
		)
		client, ctx, cancel, err := commands.NewClient(context)
		if err != nil {
			return err
		}
		defer cancel()
		containers, err := client.Containers(ctx, filters...)
		if err != nil {
			return err
		}
		if quiet {
			for _, c := range containers {
				fmt.Printf("%s\n", c.ID())
			}
			return nil
		}
		w := tabwriter.NewWriter(os.Stdout, 4, 8, 4, ' ', 0)
		fmt.Fprintln(w, "CONTAINER\tIMAGE\tRUNTIME\t")
		for _, c := range containers {
			info, err := c.Info(ctx)
			if err != nil {
				return err
			}
			imageName := info.Image
			if imageName == "" {
				imageName = "-"
			}
			if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t\n",
				c.ID(),
				imageName,
				info.Runtime.Name,
			); err != nil {
				return err
			}
		}
		return w.Flush()
	},
}

var deleteCommand = cli.Command{
	Name:      "delete",
	Usage:     "delete one or more existing containers",
	ArgsUsage: "[flags] CONTAINER [CONTAINER, ...]",
	Aliases:   []string{"del", "rm"},
	Flags: []cli.Flag{
		cli.BoolFlag{
			Name:  "keep-snapshot",
			Usage: "do not clean up snapshot with container",
		},
	},
	Action: func(context *cli.Context) error {
		var exitErr error
		client, ctx, cancel, err := commands.NewClient(context)
		if err != nil {
			return err
		}
		defer cancel()
		deleteOpts := []containerd.DeleteOpts{}
		if !context.Bool("keep-snapshot") {
			deleteOpts = append(deleteOpts, containerd.WithSnapshotCleanup)
		}

		if context.NArg() == 0 {
			return errors.New("must specify at least one container to delete")
		}
		for _, arg := range context.Args() {
			if err := deleteContainer(ctx, client, arg, deleteOpts...); err != nil {
				if exitErr == nil {
					exitErr = err
				}
				log.G(ctx).WithError(err).Errorf("failed to delete container %q", arg)
			}
		}
		return exitErr
	},
}

func deleteContainer(ctx context.Context, client *containerd.Client, id string, opts ...containerd.DeleteOpts) error {
	container, err := client.LoadContainer(ctx, id)
	if err != nil {
		return err
	}
	task, err := container.Task(ctx, cio.Load)
	if err != nil {
		return container.Delete(ctx, opts...)
	}
	status, err := task.Status(ctx)
	if err != nil {
		return err
	}
	if status.Status == containerd.Stopped || status.Status == containerd.Created {
		if _, err := task.Delete(ctx); err != nil {
			return err
		}
		return container.Delete(ctx, opts...)
	}
	return fmt.Errorf("cannot delete a non stopped container: %v", status)

}

var setLabelsCommand = cli.Command{
	Name:        "label",
	Usage:       "set and clear labels for a container",
	ArgsUsage:   "[flags] CONTAINER [<key>=<value>, ...]",
	Description: "set and clear labels for a container",
	Flags:       []cli.Flag{},
	Action: func(context *cli.Context) error {
		containerID, labels := commands.ObjectWithLabelArgs(context)
		if containerID == "" {
			return errors.New("container id must be provided")
		}
		client, ctx, cancel, err := commands.NewClient(context)
		if err != nil {
			return err
		}
		defer cancel()

		container, err := client.LoadContainer(ctx, containerID)
		if err != nil {
			return err
		}

		setlabels, err := container.SetLabels(ctx, labels)
		if err != nil {
			return err
		}

		var labelStrings []string
		for k, v := range setlabels {
			labelStrings = append(labelStrings, fmt.Sprintf("%s=%s", k, v))
		}

		fmt.Println(strings.Join(labelStrings, ","))

		return nil
	},
}

var infoCommand = cli.Command{
	Name:      "info",
	Usage:     "get info about a container",
	ArgsUsage: "CONTAINER",
	Action: func(context *cli.Context) error {
		id := context.Args().First()
		if id == "" {
			return errors.New("container id must be provided")
		}
		client, ctx, cancel, err := commands.NewClient(context)
		if err != nil {
			return err
		}
		defer cancel()
		container, err := client.LoadContainer(ctx, id)
		if err != nil {
			return err
		}
		info, err := container.Info(ctx)
		if err != nil {
			return err
		}

		if info.Spec != nil && info.Spec.Value != nil {
			v, err := typeurl.UnmarshalAny(info.Spec)
			if err != nil {
				return err
			}
			commands.PrintAsJSON(struct {
				containers.Container
				Spec interface{} `json:"Spec,omitempty"`
			}{
				Container: info,
				Spec:      v,
			})
			return nil
		}
		commands.PrintAsJSON(info)
		return nil
	},
}
<|endoftext|>


<commit_before>package main
import (
	"encoding/json"
	"flag"
	"fmt"
	"github.com/APTrust/bagman"
	"github.com/APTrust/bagman/fluctus/client"
	"github.com/bitly/go-nsq"
	"github.com/op/go-logging"
	"os"
	"strings"
	"sync/atomic"
	"time"
)

type RestoreObject struct {
	BagRestorer     *bagman.BagRestorer
	ProcessStatus   *bagman.ProcessStatus
	NsqMessage      *nsq.Message
	ErrorMessage    string
	Retry           bool
	RestorationUrls []string
	key             string
}

func (object *RestoreObject) Key() (string) {
	if object.ProcessStatus == nil {
		return ""
	}
	if object.key == "" {
		key := fmt.Sprintf("%s/%s", object.ProcessStatus.Institution, object.ProcessStatus.Name)
		object.key = key[0:len(key) - 4]  // remove ".tar" extension
	}
	return object.key
}

func (object *RestoreObject) RestoredBagUrls() (string) {
	if object.RestorationUrls == nil {
		return ""
	}
	return strings.Join(object.RestorationUrls, ", ")
}

type Channels struct {
	RestoreChannel chan *RestoreObject
	ResultsChannel chan *RestoreObject
}

// Global vars.
var channels *Channels
var config bagman.Config
var messageLog *logging.Logger
var succeeded = int64(0)
var failed = int64(0)
var volume *bagman.Volume
var fluctusClient *client.Client
var syncMap *bagman.SynchronizedMap

func main() {

	loadConfig()
	messageLog.Info("Restore started")
	err := config.EnsureFluctusConfig()
	if err != nil {
		messageLog.Fatalf("Required Fluctus config vars are missing: %v", err)
	}

	fluctusClient, err = client.New(
		config.FluctusURL,
		config.FluctusAPIVersion,
		os.Getenv("FLUCTUS_API_USER"),
		os.Getenv("FLUCTUS_API_KEY"),
		messageLog)
	if err != nil {
		messageLog.Fatalf("Cannot initialize Fluctus Client: %v", err)
	}

	initVolume()
	initChannels()
	initGoRoutines()

	syncMap = bagman.NewSynchronizedMap()

	nsqConfig := nsq.NewConfig()
	nsqConfig.Set("max_in_flight", 20)
	nsqConfig.Set("heartbeat_interval", "10s")
	nsqConfig.Set("max_attempts", uint16(config.MaxRestoreAttempts))
	nsqConfig.Set("read_timeout", "60s")
	nsqConfig.Set("write_timeout", "10s")
	nsqConfig.Set("msg_timeout", "60m")
	consumer, err := nsq.NewConsumer(config.RestoreTopic,
		config.RestoreChannel, nsqConfig)
	if err != nil {
		messageLog.Fatalf(err.Error())
	}

	handler := &RestoreProcessor{}
	consumer.SetHandler(handler)
	consumer.ConnectToNSQLookupd(config.NsqLookupd)

	// This reader blocks until we get an interrupt, so our program does not exit.
	<-consumer.StopChan
}

func loadConfig() {
	// Load the config or die.
	requestedConfig := flag.String("config", "", "configuration to run")
	flag.Parse()
	config = bagman.LoadRequestedConfig(requestedConfig)
	messageLog = bagman.InitLogger(config)
}

// Set up the volume to keep track of how much disk space is
// available. We want to avoid downloading large files when
// we know ahead of time that the volume containing the tar
// directory doesn't have enough space to accomodate them.
func initVolume() {
	var err error
	volume, err = bagman.NewVolume(config.RestoreDirectory, messageLog)
	if err != nil {
		panic(err.Error())
	}
}

// Set up the channels.
func initChannels() {
	workerBufferSize := config.Workers * 10
	channels = &Channels{}
	channels.RestoreChannel = make(chan *RestoreObject, workerBufferSize)
	channels.ResultsChannel = make(chan *RestoreObject, workerBufferSize)
}

// Set up our go routines. We want to limit the number of
// go routines so we do not have 1000+ simultaneous connections
// to Fluctus. That would just cause Fluctus to crash.
func initGoRoutines() {
	for i := 0; i < config.Workers; i++ {
		go logResult()
		go doRestore()
	}
}

type RestoreProcessor struct {
}

// MessageHandler handles messages from the queue, putting each
// item into the pipleline.
func (*RestoreProcessor) HandleMessage(message *nsq.Message) error {
	message.DisableAutoResponse()
	object := RestoreObject{
		NsqMessage: message,
		Retry: true,
	}

	// Deserialize the NSQ JSON message into object.ProcessStatus
	err := json.Unmarshal(message.Body, &object.ProcessStatus)
	if err != nil {
		detailedError := fmt.Errorf(
			"[ERROR] Could not unmarshal JSON data from nsq: %v. JSON: %s",
			err, string(message.Body))
		messageLog.Error(detailedError.Error())
		message.Finish()
		return detailedError
	}

	// If this item has already been restored, or is in process
	// of being restored, just finish the message and return.
	items, err := fluctusClient.RestorationItemsGet(object.ProcessStatus.ObjectIdentifier)
	if err != nil {
		detailedError := fmt.Errorf(
			"[ERROR] Could not get current status of object %s from Fluctus: %v.",
			object.ProcessStatus.ObjectIdentifier, err)
		messageLog.Error(detailedError.Error())
		message.Finish()
		return detailedError
	}
	// How can we know if another process is currently restoring the same object?
	// addToSyncmap below will tell us if this process is already working on it.
	if len(items) == 0 {
		messageLog.Info("Marking %s as complete because Fluctus says it "+
			"has been restored, or restoration should not be retried",
			object.Key())
		message.Finish()
		return nil
	}

	// Make a note that we're working on this item
	err = addToSyncMap(&object)
	if err != nil {
		messageLog.Info("Marking %s as complete because the file is already "+
			"being restored under another message id.", object.Key())
		message.Finish()
		return nil
	}

	// Get the IntellectualObject from Fluctus & build a BagRestorer
	intelObj, err := fluctusClient.IntellectualObjectGet(object.Key(), true)
	if err != nil {
		object.ErrorMessage = fmt.Sprintf("Cannot retrieve IntellectualObject %s from Fluctus: %v",
			object.Key(), err)
		channels.ResultsChannel <- &object
		return nil
	} else {
		object.BagRestorer, err = bagman.NewBagRestorer(intelObj, config.RestoreDirectory)
		if err != nil {
			object.ErrorMessage = fmt.Sprintf("Cannot create BagRestorer for %s: %v",
				object.Key(), err)
			channels.ResultsChannel <- &object
			return nil
		}
		object.BagRestorer.SetLogger(messageLog)
		if config.CustomRestoreBucket != "" {
			object.BagRestorer.SetCustomRestoreBucket(config.CustomRestoreBucket)
		}
	}

	// Make sure we have enough disk space to build this item.
	err = volume.Reserve(uint64(intelObj.TotalFileSize() * 2))
	if err != nil {
		// Not enough room on disk
		messageLog.Warning("Requeueing %s - not enough disk space", object.Key())
		object.ErrorMessage = err.Error()
		channels.ResultsChannel <- &object
		return nil
	}

	// Mark all ProcessedItems related to this object as started
	err = fluctusClient.RestorationStatusSet(object.ProcessStatus.ObjectIdentifier,
		bagman.StageRequested, bagman.StatusStarted, false)
	if err != nil {
		detailedError := fmt.Errorf("Cannot register restoration start with Fluctus for %s: %v",
			object.Key(), err)
		object.ErrorMessage = detailedError.Error()
		channels.ResultsChannel <- &object
		return detailedError
	}

	// Now put the object into the channel for processing
	channels.RestoreChannel <- &object
	messageLog.Info("Put %s into restore channel", object.Key())
	return nil
}

func addToSyncMap(object *RestoreObject) (error) {
	// Don't start working on a message that we're already working on.
	messageId := make([]byte, nsq.MsgIDLength)
	for i := range messageId {
		messageId[i] = object.NsqMessage.ID[i]
	}
	key := object.Key()
	if syncMap.HasKey(key) && syncMap.Get(key) != string(messageId) {
		return fmt.Errorf("Bag restorer is already working on this message")
	} else {
		// Make a note that we're processing this file.
		syncMap.Add(key, string(messageId))
	}
	return nil
}

func logResult() {
	for object := range channels.ResultsChannel {
		// Mark item as resolved in Fluctus & tell the queue what happened.
		var status bagman.StatusType = bagman.StatusSuccess
		var stage bagman.StageType = bagman.StageResolve
		if object.ErrorMessage != "" {
			status = bagman.StatusFailed
			stage = bagman.StageRequested
		}
		err := fluctusClient.RestorationStatusSet(object.ProcessStatus.ObjectIdentifier,
			stage, status, false)
		if err != nil {
			// Do we really want to go through the whole process
			// of restoring this again?
			messageLog.Error("Requeueing %s because attempt to update status in Fluctus failed: %v",
				object.Key(), err)
			object.NsqMessage.Requeue(1 * time.Minute)
			atomic.AddInt64(&failed, 1)
		} else if object.ErrorMessage != "" {
			messageLog.Error("Requeueing %s: %s", object.Key(), object.ErrorMessage)
			object.NsqMessage.Requeue(1 * time.Minute)
			atomic.AddInt64(&failed, 1)
		} else {
			messageLog.Info("Restore of %s succeeded: %s", object.Key(), object.RestoredBagUrls())
			object.NsqMessage.Finish()
			atomic.AddInt64(&succeeded, 1)
		}
		// No longer working on this
		syncMap.Delete(object.Key())
		messageLog.Info("**STATS** Succeeded: %d, Failed: %d", succeeded, failed)
	}
}

func doRestore() {
	for object := range channels.RestoreChannel {
		messageLog.Info("Restoring %s", object.Key())
		urls, err := object.BagRestorer.RestoreAndPublish()
		if err != nil {
			object.ErrorMessage = fmt.Sprintf("An error occurred during the restoration process: %v",
				err)
		} else {
			object.RestorationUrls = urls
		}
		channels.ResultsChannel <- object
	}
}
<commit_msg>bag_restorer now uses ProcessUtil<commit_after>package main
import (
	"encoding/json"
	"flag"
	"fmt"
	"github.com/APTrust/bagman"
	"github.com/APTrust/bagman/processutil"
	"github.com/bitly/go-nsq"
	"strings"
	"time"
)

type RestoreObject struct {
	BagRestorer     *bagman.BagRestorer
	ProcessStatus   *bagman.ProcessStatus
	NsqMessage      *nsq.Message
	ErrorMessage    string
	Retry           bool
	RestorationUrls []string
	key             string
}

func (object *RestoreObject) Key() (string) {
	if object.ProcessStatus == nil {
		return ""
	}
	if object.key == "" {
		key := fmt.Sprintf("%s/%s", object.ProcessStatus.Institution, object.ProcessStatus.Name)
		object.key = key[0:len(key) - 4]  // remove ".tar" extension
	}
	return object.key
}

func (object *RestoreObject) RestoredBagUrls() (string) {
	if object.RestorationUrls == nil {
		return ""
	}
	return strings.Join(object.RestorationUrls, ", ")
}

type Channels struct {
	RestoreChannel chan *RestoreObject
	ResultsChannel chan *RestoreObject
}

// Global vars.
var channels *Channels
var procUtil *processutil.ProcessUtil

func main() {
	requestedConfig := flag.String("config", "", "configuration to run")
	flag.Parse()
	procUtil = processutil.NewProcessUtil(requestedConfig)

	procUtil.MessageLog.Info("Restore started")
	err := procUtil.Config.EnsureFluctusConfig()
	if err != nil {
		procUtil.MessageLog.Fatalf("Required Fluctus config vars are missing: %v", err)
	}

	initChannels()
	initGoRoutines()

	nsqConfig := nsq.NewConfig()
	nsqConfig.Set("max_in_flight", 20)
	nsqConfig.Set("heartbeat_interval", "10s")
	nsqConfig.Set("max_attempts", uint16(procUtil.Config.MaxRestoreAttempts))
	nsqConfig.Set("read_timeout", "60s")
	nsqConfig.Set("write_timeout", "10s")
	nsqConfig.Set("msg_timeout", "60m")
	consumer, err := nsq.NewConsumer(procUtil.Config.RestoreTopic,
		procUtil.Config.RestoreChannel, nsqConfig)
	if err != nil {
		procUtil.MessageLog.Fatalf(err.Error())
	}

	handler := &RestoreProcessor{}
	consumer.SetHandler(handler)
	consumer.ConnectToNSQLookupd(procUtil.Config.NsqLookupd)

	// This reader blocks until we get an interrupt, so our program does not exit.
	<-consumer.StopChan
}

// Set up the channels.
func initChannels() {
	workerBufferSize := procUtil.Config.Workers * 10
	channels = &Channels{}
	channels.RestoreChannel = make(chan *RestoreObject, workerBufferSize)
	channels.ResultsChannel = make(chan *RestoreObject, workerBufferSize)
}

// Set up our go routines. We want to limit the number of
// go routines so we do not have 1000+ simultaneous connections
// to Fluctus. That would just cause Fluctus to crash.
func initGoRoutines() {
	for i := 0; i < procUtil.Config.Workers; i++ {
		go logResult()
		go doRestore()
	}
}

type RestoreProcessor struct {
}

// MessageHandler handles messages from the queue, putting each
// item into the pipleline.
func (*RestoreProcessor) HandleMessage(message *nsq.Message) error {
	message.DisableAutoResponse()
	object := RestoreObject{
		NsqMessage: message,
		Retry: true,
	}

	// Deserialize the NSQ JSON message into object.ProcessStatus
	err := json.Unmarshal(message.Body, &object.ProcessStatus)
	if err != nil {
		detailedError := fmt.Errorf(
			"[ERROR] Could not unmarshal JSON data from nsq: %v. JSON: %s",
			err, string(message.Body))
		procUtil.MessageLog.Error(detailedError.Error())
		message.Finish()
		return detailedError
	}

	// If this item has already been restored, or is in process
	// of being restored, just finish the message and return.
	items, err := procUtil.FluctusClient.RestorationItemsGet(object.ProcessStatus.ObjectIdentifier)
	if err != nil {
		detailedError := fmt.Errorf(
			"[ERROR] Could not get current status of object %s from Fluctus: %v.",
			object.ProcessStatus.ObjectIdentifier, err)
		procUtil.MessageLog.Error(detailedError.Error())
		message.Finish()
		return detailedError
	}
	// How can we know if another process is currently restoring the same object?
	// addToSyncmap below will tell us if this process is already working on it.
	if len(items) == 0 {
		procUtil.MessageLog.Info("Marking %s as complete because Fluctus says it "+
			"has been restored, or restoration should not be retried",
			object.Key())
		message.Finish()
		return nil
	}

	// Make a note that we're working on this item
	err = procUtil.RegisterItem(object.Key(), message.ID)
	if err != nil {
		procUtil.MessageLog.Info("Marking %s as complete because the file is already "+
			"being restored under another message id.", object.Key())
		message.Finish()
		return nil
	}

	// Get the IntellectualObject from Fluctus & build a BagRestorer
	intelObj, err := procUtil.FluctusClient.IntellectualObjectGet(object.Key(), true)
	if err != nil {
		object.ErrorMessage = fmt.Sprintf("Cannot retrieve IntellectualObject %s from Fluctus: %v",
			object.Key(), err)
		channels.ResultsChannel <- &object
		return nil
	} else {
		object.BagRestorer, err = bagman.NewBagRestorer(intelObj, procUtil.Config.RestoreDirectory)
		if err != nil {
			object.ErrorMessage = fmt.Sprintf("Cannot create BagRestorer for %s: %v",
				object.Key(), err)
			channels.ResultsChannel <- &object
			return nil
		}
		object.BagRestorer.SetLogger(procUtil.MessageLog)
		if procUtil.Config.CustomRestoreBucket != "" {
			object.BagRestorer.SetCustomRestoreBucket(procUtil.Config.CustomRestoreBucket)
		}
	}

	// Make sure we have enough disk space to build this item.
	err = procUtil.Volume.Reserve(uint64(intelObj.TotalFileSize() * 2))
	if err != nil {
		// Not enough room on disk
		procUtil.MessageLog.Warning("Requeueing %s - not enough disk space", object.Key())
		object.ErrorMessage = err.Error()
		channels.ResultsChannel <- &object
		return nil
	}

	// Mark all ProcessedItems related to this object as started
	err = procUtil.FluctusClient.RestorationStatusSet(object.ProcessStatus.ObjectIdentifier,
		bagman.StageRequested, bagman.StatusStarted, false)
	if err != nil {
		detailedError := fmt.Errorf("Cannot register restoration start with Fluctus for %s: %v",
			object.Key(), err)
		object.ErrorMessage = detailedError.Error()
		channels.ResultsChannel <- &object
		return detailedError
	}

	// Now put the object into the channel for processing
	channels.RestoreChannel <- &object
	procUtil.MessageLog.Info("Put %s into restore channel", object.Key())
	return nil
}

func logResult() {
	for object := range channels.ResultsChannel {
		// Mark item as resolved in Fluctus & tell the queue what happened.
		var status bagman.StatusType = bagman.StatusSuccess
		var stage bagman.StageType = bagman.StageResolve
		if object.ErrorMessage != "" {
			status = bagman.StatusFailed
			stage = bagman.StageRequested
		}
		err := procUtil.FluctusClient.RestorationStatusSet(object.ProcessStatus.ObjectIdentifier,
			stage, status, false)
		if err != nil {
			// Do we really want to go through the whole process
			// of restoring this again?
			procUtil.MessageLog.Error("Requeueing %s because attempt to update status in Fluctus failed: %v",
				object.Key(), err)
			object.NsqMessage.Requeue(1 * time.Minute)
			procUtil.IncrementFailed()
		} else if object.ErrorMessage != "" {
			procUtil.MessageLog.Error("Requeueing %s: %s", object.Key(), object.ErrorMessage)
			object.NsqMessage.Requeue(1 * time.Minute)
			procUtil.IncrementFailed()
		} else {
			procUtil.MessageLog.Info("Restore of %s succeeded: %s", object.Key(), object.RestoredBagUrls())
			object.NsqMessage.Finish()
			procUtil.IncrementSucceeded()
		}
		// No longer working on this
		procUtil.UnregisterItem(object.Key())
		procUtil.MessageLog.Info("**STATS** Succeeded: %d, Failed: %d", procUtil.Succeeded(), procUtil.Failed())
	}
}

func doRestore() {
	for object := range channels.RestoreChannel {
		procUtil.MessageLog.Info("Restoring %s", object.Key())
		urls, err := object.BagRestorer.RestoreAndPublish()
		if err != nil {
			object.ErrorMessage = fmt.Sprintf("An error occurred during the restoration process: %v",
				err)
		} else {
			object.RestorationUrls = urls
		}
		channels.ResultsChannel <- object
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes 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 app implements a server that runs a set of active
// components.  This includes replication controllers, service endpoints and
// nodes.
//
package app

import (
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/kubernetes/pkg/controller/disruption"
)

func startDisruptionController(ctx ControllerContext) (bool, error) {
	if !ctx.AvailableResources[schema.GroupVersionResource{Group: "policy", Version: "v1beta1", Resource: "poddisruptionbudgets"}] {
		return false, nil
	}
	go disruption.NewDisruptionController(
		ctx.InformerFactory.Core().V1().Pods(),
		ctx.InformerFactory.Policy().V1beta1().PodDisruptionBudgets(),
		ctx.InformerFactory.Core().V1().ReplicationControllers(),
		ctx.InformerFactory.Extensions().V1beta1().ReplicaSets(),
		ctx.InformerFactory.Extensions().V1beta1().Deployments(),
		ctx.InformerFactory.Apps().V1beta1().StatefulSets(),
		ctx.ClientBuilder.ClientOrDie("disruption-controller"),
	).Run(ctx.Stop)
	return true, nil
}
<commit_msg>Log more verbose when DisruptionController doesn't start.<commit_after>/*
Copyright 2016 The Kubernetes 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 app implements a server that runs a set of active
// components.  This includes replication controllers, service endpoints and
// nodes.
//
package app

import (
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/kubernetes/pkg/controller/disruption"

	"github.com/golang/glog"
)

func startDisruptionController(ctx ControllerContext) (bool, error) {
	var group = "policy"
	var version = "v1beta1"
	var resource = "poddisruptionbudgets"

	if !ctx.AvailableResources[schema.GroupVersionResource{Group: group, Version: version, Resource: resource}] {
		glog.Infof(
			"Refusing to start disruption because resource %q in group %q is not available.",
			resource, group+"/"+version)
		return false, nil
	}
	go disruption.NewDisruptionController(
		ctx.InformerFactory.Core().V1().Pods(),
		ctx.InformerFactory.Policy().V1beta1().PodDisruptionBudgets(),
		ctx.InformerFactory.Core().V1().ReplicationControllers(),
		ctx.InformerFactory.Extensions().V1beta1().ReplicaSets(),
		ctx.InformerFactory.Extensions().V1beta1().Deployments(),
		ctx.InformerFactory.Apps().V1beta1().StatefulSets(),
		ctx.ClientBuilder.ClientOrDie("disruption-controller"),
	).Run(ctx.Stop)
	return true, nil
}
<|endoftext|>


<commit_before><commit_msg>Add example save.go<commit_after><|endoftext|>


<commit_before><commit_msg>formatting, & get the list of rp's every time<commit_after><|endoftext|>


<commit_before><commit_msg>Made the renter job upload behave better on Windows<commit_after><|endoftext|>


<commit_before>package utils

import (
	"gopkg.in/mgo.v2/bson"
	"strconv"
	"strings"
)

func Selector(q ...string) (r bson.M) {
	if len(q) < 1 {
		r = nil
		return
	}
	r = make(bson.M, len(q))
	for _, s := range q {
		r[s] = 1
	}
	return
}

func Deselector(q ...string) (r bson.M) {
	if len(q) < 1 {
		r = nil
		return
	}
	r = make(bson.M, len(q))
	for _, s := range q {
		r[s] = 0
	}
	return
}

func SelDeSel(sel []string, desel []string) (r bson.M) {
	if len(sel)+len(desel) < 1 {
		r = nil
		return
	}
	r = make(bson.M, len(sel)+len(desel))
	for _, s := range sel {
		r[s] = 1
	}
	for _, s := range desel {
		r[s] = 0
	}
	return
}

//
//func UpdateBsonFromMap(mapModel map[string]interface{}) (data bson.M){
//	data = bson.M{}
//	for key, value := range mapModel {
//		//var er error
//		//var rInt int64
//		//rInt, er = strconv.ParseInt(value, 10, 64)
//		//if er == nil {
//		//	data[key] = rInt
//		//	continue
//		//}
//		//var rBool bool
//		//rBool, er = strconv.ParseBool(value)
//		//if er == nil {
//		//	data[key]= rBool
//		//	continue
//		//}
//		//var rFloat float64
//		//rFloat, er = strconv.ParseFloat(value, 64)
//		//if er == nil {
//		//	data[key]= rFloat
//		//	continue
//		//}
//		data[key] = value
//	}
//	data = bson.M{"$set":data}
//	return
//}

func GetBsonFindArray(and []map[string]string, or []map[string]string) (query bson.M) {
	query = bson.M{}
	andArray := []bson.M{}
	for _, obj := range and {
		for key, value := range obj {
			var er error
			var rInt int64
			var opr string = ""
			if strings.HasPrefix(value, ">=") {
				values := strings.Split(value, ">=")
				opr = "$gte"
				value = values[1]
			} else if strings.HasPrefix(value, ">") {
				values := strings.Split(value, ">")
				opr = "$gt"
				value = values[1]
			} else if strings.HasPrefix(value, "<=") {
				values := strings.Split(value, "<=")
				opr = "$lte"
				value = values[1]
			} else if strings.HasPrefix(value, "<") {
				values := strings.Split(value, "<")
				opr = "$lt"
				value = values[1]
			}

			if strings.HasPrefix(value, "in:") {
				values := strings.Split(value, ":")
				values = strings.Split(values[1], ",")
				andArray = append(andArray, bson.M{key: bson.M{"$in": values}})
				continue
			}

			rInt, er = strconv.ParseInt(value, 10, 64)
			if er == nil {
				if opr == "" {
					andArray = append(andArray, bson.M{key: rInt})
				} else {
					andArray = append(andArray, bson.M{key: bson.M{opr: rInt}})
				}
				continue
			}
			var rBool bool
			rBool, er = strconv.ParseBool(value)
			if er == nil {
				if opr == "" {
					andArray = append(andArray, bson.M{key: rBool})
				} else {
					andArray = append(andArray, bson.M{key: bson.M{opr: rBool}})
				}
				continue
			}
			var rFloat float64
			rFloat, er = strconv.ParseFloat(value, 64)
			if er == nil {
				if opr == "" {
					andArray = append(andArray, bson.M{key: rFloat})
				} else {
					andArray = append(andArray, bson.M{key: bson.M{opr: rFloat}})
				}
				continue
			}
			if strings.HasPrefix(value, "ObjectId(") && strings.HasSuffix(value, ")") {
				value = strings.Split(strings.SplitAfter(value, "(")[1], ")")[0]
				if bson.IsObjectIdHex(value) {
					andArray = append(andArray, bson.M{key: bson.ObjectIdHex(value)})
					continue
				}
			}
			andArray = append(andArray, bson.M{key: bson.M{"$regex": value}})
		}
	}

	orArray := []bson.M{}
	for _, obj := range or {
		for key, value := range obj {
			var er error
			var rInt int64
			var opr string = ""
			if strings.HasPrefix(value, ">") {
				values := strings.Split(value, ">")
				opr = "$gt"
				value = values[1]
			} else if strings.HasPrefix(value, ">=") {
				values := strings.Split(value, ">=")
				opr = "$gte"
				value = values[1]
			} else if strings.HasPrefix(value, "<") {
				values := strings.Split(value, "<")
				opr = "$lt"
				value = values[1]
			} else if strings.HasPrefix(value, "<=") {
				values := strings.Split(value, "<=")
				opr = "$lte"
				value = values[1]
			}

			if strings.HasPrefix(value, "in:") {
				values := strings.Split(value, ":")
				values = strings.Split(values[1], ",")
				andArray = append(andArray, bson.M{key: bson.M{"$in": values}})
				continue
			}

			rInt, er = strconv.ParseInt(value, 10, 64)
			if er == nil {
				if opr == "" {
					orArray = append(orArray, bson.M{key: rInt})
				} else {
					orArray = append(orArray, bson.M{key: bson.M{opr: rInt}})
				}
				continue
			}
			var rBool bool
			rBool, er = strconv.ParseBool(value)
			if er == nil {
				if opr == "" {
					orArray = append(orArray, bson.M{key: rBool})
				} else {
					orArray = append(orArray, bson.M{key: bson.M{opr: rBool}})
				}
				continue
			}
			var rFloat float64
			rFloat, er = strconv.ParseFloat(value, 64)
			if er == nil {
				if opr == "" {
					orArray = append(orArray, bson.M{key: rFloat})
				} else {
					orArray = append(orArray, bson.M{key: bson.M{opr: rFloat}})
				}
				continue
			}
			if strings.HasPrefix(value, "ObjectId(") && strings.HasSuffix(value, ")") {
				value = strings.Split(strings.SplitAfter(value, "(")[1], ")")[0]
				if bson.IsObjectIdHex(value) {
					orArray = append(orArray, bson.M{key: bson.ObjectIdHex(value)})
					continue
				}
			}
			orArray = append(orArray, bson.M{key: bson.M{"$regex": value}})
		}
	}

	if len(andArray) > 0 && len(orArray) > 0 {
		query = bson.M{"$and": []bson.M{{"$and": andArray}, {"$or": orArray}}}
	} else if len(andArray) > 0 && len(orArray) == 0 {
		query = bson.M{"$and": andArray}
	} else if len(andArray) == 0 && len(orArray) > 0 {
		query = bson.M{"$or": orArray}
	}
	return
}
<commit_msg>date comparision added<commit_after>package utils

import (
	"gopkg.in/mgo.v2/bson"
	"strconv"
	"strings"
	"time"
)

func Selector(q ...string) (r bson.M) {
	if len(q) < 1 {
		r = nil
		return
	}
	r = make(bson.M, len(q))
	for _, s := range q {
		r[s] = 1
	}
	return
}

func Deselector(q ...string) (r bson.M) {
	if len(q) < 1 {
		r = nil
		return
	}
	r = make(bson.M, len(q))
	for _, s := range q {
		r[s] = 0
	}
	return
}

func SelDeSel(sel []string, desel []string) (r bson.M) {
	if len(sel)+len(desel) < 1 {
		r = nil
		return
	}
	r = make(bson.M, len(sel)+len(desel))
	for _, s := range sel {
		r[s] = 1
	}
	for _, s := range desel {
		r[s] = 0
	}
	return
}

//
//func UpdateBsonFromMap(mapModel map[string]interface{}) (data bson.M){
//	data = bson.M{}
//	for key, value := range mapModel {
//		//var er error
//		//var rInt int64
//		//rInt, er = strconv.ParseInt(value, 10, 64)
//		//if er == nil {
//		//	data[key] = rInt
//		//	continue
//		//}
//		//var rBool bool
//		//rBool, er = strconv.ParseBool(value)
//		//if er == nil {
//		//	data[key]= rBool
//		//	continue
//		//}
//		//var rFloat float64
//		//rFloat, er = strconv.ParseFloat(value, 64)
//		//if er == nil {
//		//	data[key]= rFloat
//		//	continue
//		//}
//		data[key] = value
//	}
//	data = bson.M{"$set":data}
//	return
//}

func GetBsonFindArray(and []map[string]string, or []map[string]string) (query bson.M) {
	query = bson.M{}
	andArray := []bson.M{}
	for _, obj := range and {
		for key, value := range obj {
			var er error
			var rInt int64
			var opr string = ""
			if strings.HasPrefix(value, ">=") {
				values := strings.Split(value, ">=")
				opr = "$gte"
				value = values[1]
			} else if strings.HasPrefix(value, ">") {
				values := strings.Split(value, ">")
				opr = "$gt"
				value = values[1]
			} else if strings.HasPrefix(value, "<=") {
				values := strings.Split(value, "<=")
				opr = "$lte"
				value = values[1]
			} else if strings.HasPrefix(value, "<") {
				values := strings.Split(value, "<")
				opr = "$lt"
				value = values[1]
			} else if strings.HasPrefix(value, "!=") {
				values := strings.Split(value, "!=")
				opr = "$ne"
				value = values[1]
			} else if strings.HasPrefix(value, "==") {
				values := strings.Split(value, "==")
				opr = "$eq"
				value = values[1]
			}

			layout := "2006-01-02T15:04:05.999-07:00"
			t, err := time.Parse(layout, value)
			if err == nil {
				if opr == "" {
					andArray = append(andArray, bson.M{key: t})
				} else {
					andArray = append(andArray, bson.M{key: bson.M{opr: t}})
				}
				continue
			}

			if strings.HasPrefix(value, "in:") {
				values := strings.Split(value, ":")
				values = strings.Split(values[1], ",")
				andArray = append(andArray, bson.M{key: bson.M{"$in": values}})
				continue
			}

			rInt, er = strconv.ParseInt(value, 10, 64)
			if er == nil {
				if opr == "" {
					andArray = append(andArray, bson.M{key: rInt})
				} else {
					andArray = append(andArray, bson.M{key: bson.M{opr: rInt}})
				}
				continue
			}
			var rBool bool
			rBool, er = strconv.ParseBool(value)
			if er == nil {
				if opr == "" {
					andArray = append(andArray, bson.M{key: rBool})
				} else {
					andArray = append(andArray, bson.M{key: bson.M{opr: rBool}})
				}
				continue
			}
			var rFloat float64
			rFloat, er = strconv.ParseFloat(value, 64)
			if er == nil {
				if opr == "" {
					andArray = append(andArray, bson.M{key: rFloat})
				} else {
					andArray = append(andArray, bson.M{key: bson.M{opr: rFloat}})
				}
				continue
			}
			if strings.HasPrefix(value, "ObjectId(") && strings.HasSuffix(value, ")") {
				value = strings.Split(strings.SplitAfter(value, "(")[1], ")")[0]
				if bson.IsObjectIdHex(value) {
					andArray = append(andArray, bson.M{key: bson.ObjectIdHex(value)})
					continue
				}
			}
			andArray = append(andArray, bson.M{key: bson.M{"$regex": value}})
		}
	}

	orArray := []bson.M{}
	for _, obj := range or {
		for key, value := range obj {
			var er error
			var rInt int64
			var opr string = ""
			if strings.HasPrefix(value, ">") {
				values := strings.Split(value, ">")
				opr = "$gt"
				value = values[1]
			} else if strings.HasPrefix(value, ">=") {
				values := strings.Split(value, ">=")
				opr = "$gte"
				value = values[1]
			} else if strings.HasPrefix(value, "<") {
				values := strings.Split(value, "<")
				opr = "$lt"
				value = values[1]
			} else if strings.HasPrefix(value, "<=") {
				values := strings.Split(value, "<=")
				opr = "$lte"
				value = values[1]
			} else if strings.HasPrefix(value, "!=") {
				values := strings.Split(value, "!=")
				opr = "$ne"
				value = values[1]
			} else if strings.HasPrefix(value, "==") {
				values := strings.Split(value, "==")
				opr = "$eq"
				value = values[1]
			}

			layout := "2006-01-02T15:04:05.999-07:00"
			t, err := time.Parse(layout, value)
			if err == nil {
				if opr == "" {
					andArray = append(andArray, bson.M{key: t})
				} else {
					andArray = append(andArray, bson.M{key: bson.M{opr: t}})
				}
				continue
			}

			if strings.HasPrefix(value, "in:") {
				values := strings.Split(value, ":")
				values = strings.Split(values[1], ",")
				andArray = append(andArray, bson.M{key: bson.M{"$in": values}})
				continue
			}

			rInt, er = strconv.ParseInt(value, 10, 64)
			if er == nil {
				if opr == "" {
					orArray = append(orArray, bson.M{key: rInt})
				} else {
					orArray = append(orArray, bson.M{key: bson.M{opr: rInt}})
				}
				continue
			}
			var rBool bool
			rBool, er = strconv.ParseBool(value)
			if er == nil {
				if opr == "" {
					orArray = append(orArray, bson.M{key: rBool})
				} else {
					orArray = append(orArray, bson.M{key: bson.M{opr: rBool}})
				}
				continue
			}
			var rFloat float64
			rFloat, er = strconv.ParseFloat(value, 64)
			if er == nil {
				if opr == "" {
					orArray = append(orArray, bson.M{key: rFloat})
				} else {
					orArray = append(orArray, bson.M{key: bson.M{opr: rFloat}})
				}
				continue
			}
			if strings.HasPrefix(value, "ObjectId(") && strings.HasSuffix(value, ")") {
				value = strings.Split(strings.SplitAfter(value, "(")[1], ")")[0]
				if bson.IsObjectIdHex(value) {
					orArray = append(orArray, bson.M{key: bson.ObjectIdHex(value)})
					continue
				}
			}
			orArray = append(orArray, bson.M{key: bson.M{"$regex": value}})
		}
	}

	if len(andArray) > 0 && len(orArray) > 0 {
		query = bson.M{"$and": []bson.M{{"$and": andArray}, {"$or": orArray}}}
	} else if len(andArray) > 0 && len(orArray) == 0 {
		query = bson.M{"$and": andArray}
	} else if len(andArray) == 0 && len(orArray) > 0 {
		query = bson.M{"$or": orArray}
	}
	return
}
<|endoftext|>


<commit_before>package cli

import (
	"fmt"
	"os"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/bitrise-io/bitrise/bitrise"
	"github.com/bitrise-io/bitrise/models"
	"github.com/bitrise-io/go-utils/colorstring"
	"github.com/codegangsta/cli"
)

func printAvailableTriggerFilters(triggerMap []models.TriggerMapItemModel) {
	log.Infoln("The following trigger filters are available:")
	for _, triggerItem := range triggerMap {
		log.Infoln(" * " + triggerItem.Pattern)
	}

	fmt.Println()
	log.Infoln("You can trigger a workflow with:")
	log.Infoln("-> bitrise trigger the-trigger-filter")
	fmt.Println()

	os.Exit(1)
}

func trigger(c *cli.Context) {
	PrintBitriseHeaderASCIIArt(c.App.Version)

	if !bitrise.CheckIsSetupWasDoneForVersion(c.App.Version) {
		log.Warnln(colorstring.Yellow("Setup was not performed for this version of bitrise, doing it now..."))
		if err := bitrise.RunSetup(c.App.Version, false); err != nil {
			log.Fatalln("Setup failed:", err)
		}
	}

	startTime := time.Now()

	// ------------------------
	// Input validation

	// Inventory validation
	inventoryEnvironments, err := CreateInventoryFromCLIParams(c)
	if err != nil {
		log.Fatalf("Failed to create inventory, err: %s", err)
	}

	// Config validation
	bitriseConfig, err := CreateBitriseConfigFromCLIParams(c)
	if err != nil {
		log.Fatalf("Failed to create bitrise cofing, err: %s", err)
	}

	// Trigger filter validation
	triggerPattern := ""
	if len(c.Args()) < 1 {
		log.Errorln("No workfow specified!")
	} else {
		triggerPattern = c.Args()[0]
	}

	if triggerPattern == "" {
		// no trigger filter specified
		//  list all the available ones and then exit
		printAvailableTriggerFilters(bitriseConfig.TriggerMap)
	}

	pullRequestID := os.Getenv(bitrise.PullRequestIDEnvKey)
	workflowToRunID, found, err := bitriseConfig.WorkflowIDByPattern(triggerPattern, pullRequestID)
	if err != nil {
		log.Fatalf("Faild to select workflow by pattern (%s), err: %s", triggerPattern, err)
	}
	if !found {
		log.Warnf("No workflow id match, to pattern (%s), try to use pattern as workflow id", triggerPattern)
		workflowToRunID = triggerPattern
	}
	log.Infof("Pattern (%s) triggered workflow (%s) ", triggerPattern, workflowToRunID)

	// Run selected configuration
	if _, err := runWorkflowWithConfiguration(startTime, workflowToRunID, bitriseConfig, inventoryEnvironments); err != nil {
		log.Fatalln("Error: ", err)
	}
}
<commit_msg>PR fix<commit_after>package cli

import (
	"fmt"
	"os"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/bitrise-io/bitrise/bitrise"
	"github.com/bitrise-io/bitrise/models"
	"github.com/bitrise-io/go-utils/colorstring"
	"github.com/codegangsta/cli"
)

func printAvailableTriggerFilters(triggerMap []models.TriggerMapItemModel) {
	log.Infoln("The following trigger filters are available:")
	for _, triggerItem := range triggerMap {
		log.Infoln(" * " + triggerItem.Pattern)
	}

	fmt.Println()
	log.Infoln("You can trigger a workflow with:")
	log.Infoln("-> bitrise trigger the-trigger-filter")
	fmt.Println()

	os.Exit(1)
}

func trigger(c *cli.Context) {
	PrintBitriseHeaderASCIIArt(c.App.Version)

	if !bitrise.CheckIsSetupWasDoneForVersion(c.App.Version) {
		log.Warnln(colorstring.Yellow("Setup was not performed for this version of bitrise, doing it now..."))
		if err := bitrise.RunSetup(c.App.Version, false); err != nil {
			log.Fatalln("Setup failed:", err)
		}
	}

	startTime := time.Now()

	// ------------------------
	// Input validation

	// Inventory validation
	inventoryEnvironments, err := CreateInventoryFromCLIParams(c)
	if err != nil {
		log.Fatalf("Failed to create inventory, err: %s", err)
	}

	// Config validation
	bitriseConfig, err := CreateBitriseConfigFromCLIParams(c)
	if err != nil {
		log.Fatalf("Failed to create bitrise cofing, err: %s", err)
	}

	// Trigger filter validation
	triggerPattern := ""
	if len(c.Args()) < 1 {
		log.Errorln("No workfow specified!")
	} else {
		triggerPattern = c.Args()[0]
	}

	if triggerPattern == "" {
		// no trigger filter specified
		//  list all the available ones and then exit
		printAvailableTriggerFilters(bitriseConfig.TriggerMap)
	}

	pullRequestID := os.Getenv(bitrise.PullRequestIDEnvKey)
	workflowToRunID, found, err := bitriseConfig.WorkflowIDByPattern(triggerPattern, pullRequestID)
	if err != nil {
		log.Fatalf("Faild to select workflow by pattern (%s), err: %s", triggerPattern, err)
	}
	if !found {
		log.Warnf("No match found in the trigger_map for pattern (%s), trying to use pattern as workflow ID...", triggerPattern)
		workflowToRunID = triggerPattern
	}
	log.Infof("Pattern (%s) triggered workflow (%s) ", triggerPattern, workflowToRunID)

	// Run selected configuration
	if _, err := runWorkflowWithConfiguration(startTime, workflowToRunID, bitriseConfig, inventoryEnvironments); err != nil {
		log.Fatalln("Error: ", err)
	}
}
<|endoftext|>


<commit_before>package colonycore

import (
	"github.com/eaciit/errorlib"
	// "github.com/eaciit/orm/v1"
	"github.com/eaciit/toolkit"
	// "log"
	"strconv"
	"strings"
	"time"
)

const (
	/*FILE_TYPE = map[string]string{
		"-": "FILE",
		"d": "DIRECTORY",
		"l": "SYMLINK",
	}*/
	DELIMITER = "/"
)

type FileInfo struct {
	Name         string      `json:"name", bson:"name"`
	Size         float64     `json:"size", bson:"size"`
	User         string      `json:"user", bson:"user"`
	Group        string      `json:"group", bson:"group"`
	Permissions  string      `json:"permissions", bson:"permissions"`
	CreatedDate  time.Time   `json:"createddate", bson:"createddate"`
	LastModified time.Time   `json:"lastmodified", bson:"lastmodified"`
	Type         string      `json:"type", bson:"type"`
	Sub          []*FileInfo `json:"sub", bson:"sub"`
	SubCount     int64       `json:"subcount", bson:"subcount"`
	IsDir        bool        `json:"isdir", bson:"isdir"`
	Path         string      `json:"path", bson:"path"`
}

func ConstructFileInfo(lines string, path string) ([]FileInfo, error) {
	var result []FileInfo

	aLine := strings.Split(lines, "\n")

	if len(aLine) > 2 {
		for _, val := range aLine[1:] {
			if val != "" {
				res, e := parse(val, path)
				if e != nil {
					return result, e
				} else {
					result = append(result, res)
				}
			}
		}

		return result, nil
	} else {
		res, e := parse(aLine[1], path)
		result = append(result, res)
		return result, e
	}

}

func parse(line string, path string) (result FileInfo, e error) {
	if line != "" {
		cols := strings.Split(strings.Trim(line, " "), "||")

		if len(cols) == 9 {
			result.Type = strings.TrimSpace(cols[0][:1])
			result.Sub = nil

			if result.Type == "d" {
				result.IsDir = true
			}

			result.Permissions = strings.TrimSpace(cols[0][1:])

			subCount, _ := strconv.ParseInt(strings.TrimSpace(cols[1]), 10, 64)
			result.SubCount = subCount

			result.User = strings.TrimSpace(cols[2])

			result.Group = strings.TrimSpace(cols[3])

			size, _ := strconv.ParseFloat(strings.TrimSpace(cols[4]), 64)
			result.Size = size

			var lastModified time.Time
			str := strings.TrimSpace(cols[5]) + "-" + strings.TrimSpace(cols[6]) + "-" + strings.TrimSpace(cols[7])

			if len(strings.TrimSpace(cols[7])) == 5 {
				str = str + "-" + strconv.Itoa(time.Now().Year())
				lastModified = toolkit.String2Date(str, "MMM-d-H:mm-YYYY")
			} else {
				lastModified = toolkit.String2Date(str, "MMM-d-YYYY")
			}

			result.LastModified = lastModified

			result.Name = strings.TrimSpace(cols[8])

			if path[len(path)-1:] != DELIMITER {
				path = path + DELIMITER
			}

			result.Path = path + result.Name

		} else {
			e = errorlib.Error("", "", "parse", "column is not valid")
		}
	}

	return
}
<commit_msg>add iseditable<commit_after>package colonycore

import (
	"github.com/eaciit/errorlib"
	// "github.com/eaciit/orm/v1"
	"github.com/eaciit/toolkit"
	// "log"
	"strconv"
	"strings"
	"time"
)

const (
	/*FILE_TYPE = map[string]string{
		"-": "FILE",
		"d": "DIRECTORY",
		"l": "SYMLINK",
	}*/
	DELIMITER = "/"
)

type FileInfo struct {
	Name         string      `json:"name", bson:"name"`
	Size         float64     `json:"size", bson:"size"`
	User         string      `json:"user", bson:"user"`
	Group        string      `json:"group", bson:"group"`
	Permissions  string      `json:"permissions", bson:"permissions"`
	CreatedDate  time.Time   `json:"createddate", bson:"createddate"`
	LastModified time.Time   `json:"lastmodified", bson:"lastmodified"`
	Type         string      `json:"type", bson:"type"`
	Sub          []*FileInfo `json:"sub", bson:"sub"`
	SubCount     int64       `json:"subcount", bson:"subcount"`
	IsDir        bool        `json:"isdir", bson:"isdir"`
	Path         string      `json:"path", bson:"path"`
	IsEditable   bool        `json:"iseditable", bson:"iseditable"`
}

func ConstructFileInfo(lines string, path string) ([]FileInfo, error) {
	var result []FileInfo

	aLine := strings.Split(lines, "\n")

	if len(aLine) > 1 {
		for _, val := range aLine {
			if val != "" {
				res, e := parse(val, path)
				if e != nil {
					return result, e
				} else {
					result = append(result, res)
				}
			}
		}

		return result, nil
	} else if len(aLine) > 0 {
		res, e := parse(aLine[0], path)
		result = append(result, res)
		return result, e
	}
	return result, nil
}

func parse(line string, path string) (result FileInfo, e error) {
	if line != "" {
		cols := strings.Split(strings.Trim(line, " "), "||")

		if len(cols) == 9 {
			result.Type = strings.TrimSpace(cols[0][:1])
			result.Sub = nil

			if result.Type == "d" {
				result.IsDir = true
			}

			result.Permissions = strings.TrimSpace(cols[0][1:])

			subCount, _ := strconv.ParseInt(strings.TrimSpace(cols[1]), 10, 64)
			result.SubCount = subCount

			result.User = strings.TrimSpace(cols[2])

			result.Group = strings.TrimSpace(cols[3])

			size, _ := strconv.ParseFloat(strings.TrimSpace(cols[4]), 64)
			result.Size = size

			var lastModified time.Time
			str := strings.TrimSpace(cols[5]) + "-" + strings.TrimSpace(cols[6]) + "-" + strings.TrimSpace(cols[7])

			if len(strings.TrimSpace(cols[7])) == 5 {
				str = str + "-" + strconv.Itoa(time.Now().Year())
				lastModified = toolkit.String2Date(str, "MMM-d-H:mm-YYYY")
			} else {
				lastModified = toolkit.String2Date(str, "MMM-d-YYYY")
			}

			result.LastModified = lastModified

			result.Name = strings.TrimSpace(cols[8])

			if len(path) > 1 {
				if path[len(path)-1:] != DELIMITER {
					path = path + DELIMITER
				}

				result.Path = path + result.Name
			}

			result.IsEditable = true
		} else {
			e = errorlib.Error("", "", "parse", "column is not valid")
		}
	}

	return
}
<|endoftext|>


<commit_before>package gtd

import (
	"fmt"
	"os"
	"path/filepath"

	mapset "github.com/deckarep/golang-set"
	"github.com/urfave/cli"
)

const (
	ExitCodeOK        int = iota // 0
	ExitCodeError                // 1
	ExitCodeFileError            // 2
)

var Version = "0.1.0"

func Run(args []string) int {
	app := cli.NewApp()
	app.Name = "gtd"
	app.Version = Version
	app.Usage = "todo app"
	app.Author = "Takuya Omura"
	app.Email = "t.omura8383@gmail.com"
	app.Commands = []cli.Command{
		{
			Name:    "add",
			Aliases: []string{"n"},
			Action:  addTodoAction,
			Usage:   "add todo",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "parent, p",
					Usage: "specify parent `TODO_NUM` (ex: gtd add -p 1 task)",
				},
				cli.StringFlag{
					Name:  "tag, t",
					Usage: "add `TAG` to todo (ex: gtd add -t memo task)",
				},
			},
		},
		{
			Name:    "list",
			Aliases: []string{"l"},
			Usage:   "list todo",
			Action:  listTodoAction,
			Flags: []cli.Flag{
				cli.BoolFlag{
					Name:  "all, a",
					Usage: "show all todos",
				},
			},
		},
		{
			Name:    "tags",
			Aliases: []string{"t"},
			Usage:   "list tags",
			Action:  tagTodoAction,
			Flags: []cli.Flag{
				cli.BoolFlag{
					Name:  "all, a",
					Usage: "show all tags",
				},
			},
		},
		{
			Name:    "done",
			Aliases: []string{"d"},
			Usage:   "done todo",
			Action:  doneTodoAction,
		},
		{
			Name:    "clean",
			Aliases: []string{"c"},
			Usage:   "clean done todo",
			Action:  cleanTodoAction,
		},
		{
			Name:    "setting",
			Aliases: []string{"s"},
			Usage:   "edit config file",
			Action:  settingTodoAction,
		},
	}
	return msg(app.Run(os.Args))
}

func msg(err error) int {
	if err != nil {
		fmt.Fprintf(os.Stderr, "%s: %v\n", os.Args[0], err)
		return ExitCodeError
	}
	return ExitCodeOK
}

func addTodoAction(c *cli.Context) error {
	var title string
	var tag string
	var parentnum string
	var memo string
	var todos Todos

	var cfg config
	err := cfg.load()
	if err != nil {
		return fmt.Errorf("falid to load configfile: %v", err)
	}

	err = todos.UnmarshallJson(cfg.GtdFile)
	if err != nil {
		return fmt.Errorf("Failed to read todofile: %v", err)
	}

	if c.Args().Present() {
		title = c.Args().First()
		tag = c.String("tag")
		parentnum = c.String("parent")
		memo = c.String("memo")
	} else {
		cli.ShowCommandHelp(c, "add")
		return fmt.Errorf("Failed to parse options")
	}

	todo := Todo{
		Title: title,
		Done:  false,
		Tag:   tag,
		Memo:  memo,
	}

	parentnumlist, err := parseTodoNum(parentnum)
	todolist, err := appendTodo(todos.Todos, todo, parentnumlist)
	if err != nil {
		return fmt.Errorf("Failed to append task: %v", err)
	}
	todos.SetTodos(todolist)

	return todos.marshallJson(cfg.GtdFile)
}

func listTodoAction(c *cli.Context) error {
	var cfg config
	var todos Todos

	err := cfg.load()
	if err != nil {
		return fmt.Errorf("falid to load configfile: %v", err)
	}
	err = todos.UnmarshallJson(cfg.GtdFile)
	if err != nil {
		return fmt.Errorf("Failed to read jsonfile: %v", err)
	}
	fmt.Println("\u2705  YOUR TO DO LIST")

	if c.Bool("all") {
		displayAllTodo(todos.Todos, "")
		return nil
	}
	displayTodo(todos.Todos, "")
	return nil
}

func tagTodoAction(c *cli.Context) error {
	var cfg config
	var todos Todos
	err := cfg.load()
	if err != nil {
		return fmt.Errorf("falid to load configfile: %v", err)
	}
	err = todos.UnmarshallJson(cfg.GtdFile)
	if err != nil {
		return fmt.Errorf("failed to read jsonfile: %v", err)
	}

	if c.Bool("all") {
		tags := mapset.NewSet()
		displayAllTags(todos.Todos, tags)
	} else if tag := c.Args().First(); tag != "" {
		displayTagTodo(todos.Todos, tag)
	} else {
		cli.ShowCommandHelp(c, "list")
		return fmt.Errorf("Failed to parse options")
	}
	return nil
}

func doneTodoAction(c *cli.Context) error {
	var todos Todos

	var cfg config
	err := cfg.load()
	if err != nil {
		return fmt.Errorf("falid to load configfile: %v", err)
	}

	if !c.Args().Present() {
		return fmt.Errorf("Failed to parse options")
	}

	err = todos.UnmarshallJson(cfg.GtdFile)
	if err != nil {
		return fmt.Errorf("failed to read jsonfile: %v", err)
	}

	todonum := c.Args().First()
	todonumlist, err := parseTodoNum(todonum)
	if err != nil {
		return fmt.Errorf("Failed to parse number: %v", err)
	}
	todolist, err := doneTodo(todos.Todos, todonumlist)
	if err != nil {
		return fmt.Errorf("Failed to done todo: %v", err)
	}
	todos.SetTodos(todolist)

	return todos.marshallJson(cfg.GtdFile)
}

func cleanTodoAction(c *cli.Context) error {
	var cfg config
	var todos Todos
	err := cfg.load()
	if err != nil {
		return fmt.Errorf("falid to load configfile: %v", err)
	}
	err = todos.UnmarshallJson(cfg.GtdFile)
	if err != nil {
		return fmt.Errorf("failed to read jsonfile: %v", err)
	}
	todolist, err := cleanAllTodos(todos.Todos)
	todos.SetTodos(todolist)

	return todos.marshallJson(cfg.GtdFile)
}

func settingTodoAction(c *cli.Context) error {
	var cfg config
	err := cfg.load()
	if err != nil {
		return fmt.Errorf("falid to load configfile: %v", err)
	}
	dir := os.Getenv("HOME")
	dir = filepath.Join(dir, ".config", "gtd")
	file := filepath.Join(dir, "config.toml")
	return cfg.runcmd(cfg.Editor, file)
}
<commit_msg>fix add aliases<commit_after>package gtd

import (
	"fmt"
	"os"
	"path/filepath"

	mapset "github.com/deckarep/golang-set"
	"github.com/urfave/cli"
)

const (
	ExitCodeOK        int = iota // 0
	ExitCodeError                // 1
	ExitCodeFileError            // 2
)

var Version = "0.1.0"

func Run(args []string) int {
	app := cli.NewApp()
	app.Name = "gtd"
	app.Version = Version
	app.Usage = "todo app"
	app.Author = "Takuya Omura"
	app.Email = "t.omura8383@gmail.com"
	app.Commands = []cli.Command{
		{
			Name:    "add",
			Aliases: []string{"a"},
			Action:  addTodoAction,
			Usage:   "add todo",
			Flags: []cli.Flag{
				cli.StringFlag{
					Name:  "parent, p",
					Usage: "specify parent `TODO_NUM` (ex: gtd add -p 1 task)",
				},
				cli.StringFlag{
					Name:  "tag, t",
					Usage: "add `TAG` to todo (ex: gtd add -t memo task)",
				},
			},
		},
		{
			Name:    "list",
			Aliases: []string{"l"},
			Usage:   "list todo",
			Action:  listTodoAction,
			Flags: []cli.Flag{
				cli.BoolFlag{
					Name:  "all, a",
					Usage: "show all todos",
				},
			},
		},
		{
			Name:    "tags",
			Aliases: []string{"t"},
			Usage:   "list tags",
			Action:  tagTodoAction,
			Flags: []cli.Flag{
				cli.BoolFlag{
					Name:  "all, a",
					Usage: "show all tags",
				},
			},
		},
		{
			Name:    "done",
			Aliases: []string{"d"},
			Usage:   "done todo",
			Action:  doneTodoAction,
		},
		{
			Name:    "clean",
			Aliases: []string{"c"},
			Usage:   "clean done todo",
			Action:  cleanTodoAction,
		},
		{
			Name:    "setting",
			Aliases: []string{"s"},
			Usage:   "edit config file",
			Action:  settingTodoAction,
		},
	}
	return msg(app.Run(os.Args))
}

func msg(err error) int {
	if err != nil {
		fmt.Fprintf(os.Stderr, "%s: %v\n", os.Args[0], err)
		return ExitCodeError
	}
	return ExitCodeOK
}

func addTodoAction(c *cli.Context) error {
	var title string
	var tag string
	var parentnum string
	var memo string
	var todos Todos

	var cfg config
	err := cfg.load()
	if err != nil {
		return fmt.Errorf("falid to load configfile: %v", err)
	}

	err = todos.UnmarshallJson(cfg.GtdFile)
	if err != nil {
		return fmt.Errorf("Failed to read todofile: %v", err)
	}

	if c.Args().Present() {
		title = c.Args().First()
		tag = c.String("tag")
		parentnum = c.String("parent")
		memo = c.String("memo")
	} else {
		cli.ShowCommandHelp(c, "add")
		return fmt.Errorf("Failed to parse options")
	}

	todo := Todo{
		Title: title,
		Done:  false,
		Tag:   tag,
		Memo:  memo,
	}

	parentnumlist, err := parseTodoNum(parentnum)
	todolist, err := appendTodo(todos.Todos, todo, parentnumlist)
	if err != nil {
		return fmt.Errorf("Failed to append task: %v", err)
	}
	todos.SetTodos(todolist)

	return todos.marshallJson(cfg.GtdFile)
}

func listTodoAction(c *cli.Context) error {
	var cfg config
	var todos Todos

	err := cfg.load()
	if err != nil {
		return fmt.Errorf("falid to load configfile: %v", err)
	}
	err = todos.UnmarshallJson(cfg.GtdFile)
	if err != nil {
		return fmt.Errorf("Failed to read jsonfile: %v", err)
	}
	fmt.Println("\u2705  YOUR TO DO LIST")

	if c.Bool("all") {
		displayAllTodo(todos.Todos, "")
		return nil
	}
	displayTodo(todos.Todos, "")
	return nil
}

func tagTodoAction(c *cli.Context) error {
	var cfg config
	var todos Todos
	err := cfg.load()
	if err != nil {
		return fmt.Errorf("falid to load configfile: %v", err)
	}
	err = todos.UnmarshallJson(cfg.GtdFile)
	if err != nil {
		return fmt.Errorf("failed to read jsonfile: %v", err)
	}

	if c.Bool("all") {
		tags := mapset.NewSet()
		displayAllTags(todos.Todos, tags)
	} else if tag := c.Args().First(); tag != "" {
		displayTagTodo(todos.Todos, tag)
	} else {
		cli.ShowCommandHelp(c, "list")
		return fmt.Errorf("Failed to parse options")
	}
	return nil
}

func doneTodoAction(c *cli.Context) error {
	var todos Todos

	var cfg config
	err := cfg.load()
	if err != nil {
		return fmt.Errorf("falid to load configfile: %v", err)
	}

	if !c.Args().Present() {
		return fmt.Errorf("Failed to parse options")
	}

	err = todos.UnmarshallJson(cfg.GtdFile)
	if err != nil {
		return fmt.Errorf("failed to read jsonfile: %v", err)
	}

	todonum := c.Args().First()
	todonumlist, err := parseTodoNum(todonum)
	if err != nil {
		return fmt.Errorf("Failed to parse number: %v", err)
	}
	todolist, err := doneTodo(todos.Todos, todonumlist)
	if err != nil {
		return fmt.Errorf("Failed to done todo: %v", err)
	}
	todos.SetTodos(todolist)

	return todos.marshallJson(cfg.GtdFile)
}

func cleanTodoAction(c *cli.Context) error {
	var cfg config
	var todos Todos
	err := cfg.load()
	if err != nil {
		return fmt.Errorf("falid to load configfile: %v", err)
	}
	err = todos.UnmarshallJson(cfg.GtdFile)
	if err != nil {
		return fmt.Errorf("failed to read jsonfile: %v", err)
	}
	todolist, err := cleanAllTodos(todos.Todos)
	todos.SetTodos(todolist)

	return todos.marshallJson(cfg.GtdFile)
}

func settingTodoAction(c *cli.Context) error {
	var cfg config
	err := cfg.load()
	if err != nil {
		return fmt.Errorf("falid to load configfile: %v", err)
	}
	dir := os.Getenv("HOME")
	dir = filepath.Join(dir, ".config", "gtd")
	file := filepath.Join(dir, "config.toml")
	return cfg.runcmd(cfg.Editor, file)
}
<|endoftext|>


<commit_before>// Copyright (c) Liam Stanley <me@liamstanley.io>. All rights reserved. Use
// of this source code is governed by the MIT license that can be found in
// the LICENSE file.

package girc

import (
	"errors"
	"fmt"
)

// Commands holds a large list of useful methods to interact with the server,
// and wrappers for common events.
type Commands struct {
	c *Client
}

// Nick changes the client nickname.
func (cmd *Commands) Nick(name string) error {
	if !IsValidNick(name) {
		return &ErrInvalidTarget{Target: name}
	}

	cmd.c.Send(&Event{Command: NICK, Params: []string{name}})
	return nil
}

// Join attempts to enter a list of IRC channels, at bulk if possible to
// prevent sending extensive JOIN commands.
func (cmd *Commands) Join(channels ...string) error {
	// We can join multiple channels at once, however we need to ensure that
	// we are not exceeding the line length. (see maxLength)
	max := maxLength - len(JOIN) - 1

	var buffer string

	for i := 0; i < len(channels); i++ {
		if !IsValidChannel(channels[i]) {
			return &ErrInvalidTarget{Target: channels[i]}
		}

		if len(buffer+","+channels[i]) > max {
			cmd.c.Send(&Event{Command: JOIN, Params: []string{buffer}})
			buffer = ""
			continue
		}

		if len(buffer) == 0 {
			buffer = channels[i]
		} else {
			buffer += "," + channels[i]
		}

		if i == len(channels)-1 {
			cmd.c.Send(&Event{Command: JOIN, Params: []string{buffer}})
			return nil
		}
	}

	return nil
}

// JoinKey attempts to enter an IRC channel with a password.
func (cmd *Commands) JoinKey(channel, password string) error {
	if !IsValidChannel(channel) {
		return &ErrInvalidTarget{Target: channel}
	}

	cmd.c.Send(&Event{Command: JOIN, Params: []string{channel, password}})
	return nil
}

// Part leaves an IRC channel.
func (cmd *Commands) Part(channel, message string) error {
	if !IsValidChannel(channel) {
		return &ErrInvalidTarget{Target: channel}
	}

	cmd.c.Send(&Event{Command: JOIN, Params: []string{channel}})
	return nil
}

// PartMessage leaves an IRC channel with a specified leave message.
func (cmd *Commands) PartMessage(channel, message string) error {
	if !IsValidChannel(channel) {
		return &ErrInvalidTarget{Target: channel}
	}

	cmd.c.Send(&Event{Command: JOIN, Params: []string{channel}, Trailing: message})
	return nil
}

// SendCTCP sends a CTCP request to target. Note that this method uses
// PRIVMSG specifically.
func (cmd *Commands) SendCTCP(target, ctcpType, message string) error {
	out := encodeCTCPRaw(ctcpType, message)
	if out == "" {
		return errors.New("invalid CTCP")
	}

	return cmd.Message(target, out)
}

// SendCTCPf sends a CTCP request to target using a specific format. Note that
// this method uses PRIVMSG specifically.
func (cmd *Commands) SendCTCPf(target, ctcpType, format string, a ...interface{}) error {
	return cmd.SendCTCP(target, ctcpType, fmt.Sprintf(format, a...))
}

// SendCTCPReplyf sends a CTCP response to target using a specific format.
// Note that this method uses NOTICE specifically.
func (cmd *Commands) SendCTCPReplyf(target, ctcpType, format string, a ...interface{}) error {
	return cmd.SendCTCPReply(target, ctcpType, fmt.Sprintf(format, a...))
}

// SendCTCPReply sends a CTCP response to target. Note that this method uses
// NOTICE specifically.
func (cmd *Commands) SendCTCPReply(target, ctcpType, message string) error {
	out := encodeCTCPRaw(ctcpType, message)
	if out == "" {
		return errors.New("invalid CTCP")
	}

	return cmd.Notice(target, out)
}

// Message sends a PRIVMSG to target (either channel, service, or user).
func (cmd *Commands) Message(target, message string) error {
	if !IsValidNick(target) && !IsValidChannel(target) {
		return &ErrInvalidTarget{Target: target}
	}

	cmd.c.Send(&Event{Command: PRIVMSG, Params: []string{target}, Trailing: message})
	return nil
}

// Messagef sends a formated PRIVMSG to target (either channel, service, or
// user).
func (cmd *Commands) Messagef(target, format string, a ...interface{}) error {
	return cmd.Message(target, fmt.Sprintf(format, a...))
}

// Action sends a PRIVMSG ACTION (/me) to target (either channel, service,
// or user).
func (cmd *Commands) Action(target, message string) error {
	if !IsValidNick(target) && !IsValidChannel(target) {
		return &ErrInvalidTarget{Target: target}
	}

	cmd.c.Send(&Event{
		Command:  PRIVMSG,
		Params:   []string{target},
		Trailing: fmt.Sprintf("\001ACTION %s\001", message),
	})
	return nil
}

// Actionf sends a formated PRIVMSG ACTION (/me) to target (either channel,
// service, or user).
func (cmd *Commands) Actionf(target, format string, a ...interface{}) error {
	return cmd.Action(target, fmt.Sprintf(format, a...))
}

// Notice sends a NOTICE to target (either channel, service, or user).
func (cmd *Commands) Notice(target, message string) error {
	if !IsValidNick(target) && !IsValidChannel(target) {
		return &ErrInvalidTarget{Target: target}
	}

	cmd.c.Send(&Event{Command: NOTICE, Params: []string{target}, Trailing: message})
	return nil
}

// Noticef sends a formated NOTICE to target (either channel, service, or
// user).
func (cmd *Commands) Noticef(target, format string, a ...interface{}) error {
	return cmd.Notice(target, fmt.Sprintf(format, a...))
}

// SendRaw sends a raw string back to the server, without carriage returns
// or newlines.
func (cmd *Commands) SendRaw(raw string) error {
	e := ParseEvent(raw)
	if e == nil {
		return errors.New("invalid event: " + raw)
	}

	cmd.c.Send(e)
	return nil
}

// SendRawf sends a formated string back to the server, without carriage
// returns or newlines.
func (cmd *Commands) SendRawf(format string, a ...interface{}) error {
	return cmd.SendRaw(fmt.Sprintf(format, a...))
}

// Topic sets the topic of channel to message. Does not verify the length
// of the topic.
func (cmd *Commands) Topic(channel, message string) {
	cmd.c.Send(&Event{Command: TOPIC, Params: []string{channel}, Trailing: message})
}

// Who sends a WHO query to the server, which will attempt WHOX by default.
// See http://faerion.sourceforge.net/doc/irc/whox.var for more details. This
// sends "%tcuhnr,2" per default. Do not use "1" as this will conflict with
// girc's builtin tracking functionality.
func (cmd *Commands) Who(target string) error {
	if !IsValidNick(target) && !IsValidChannel(target) && !IsValidUser(target) {
		return &ErrInvalidTarget{Target: target}
	}

	cmd.c.Send(&Event{Command: WHO, Params: []string{target, "%tcuhnr,2"}})
	return nil
}

// Whois sends a WHOIS query to the server, targeted at a specific user.
// as WHOIS is a bit slower, you may want to use WHO for brief user info.
func (cmd *Commands) Whois(nick string) error {
	if !IsValidNick(nick) {
		return &ErrInvalidTarget{Target: nick}
	}

	cmd.c.Send(&Event{Command: WHOIS, Params: []string{nick}})
	return nil
}

// Ping sends a PING query to the server, with a specific identifier that
// the server should respond with.
func (cmd *Commands) Ping(id string) {
	cmd.c.Send(&Event{Command: PING, Params: []string{id}})
}

// Pong sends a PONG query to the server, with an identifier which was
// received from a previous PING query received by the client.
func (cmd *Commands) Pong(id string) {
	cmd.c.Send(&Event{Command: PONG, Params: []string{id}})
}

// Oper sends a OPER authentication query to the server, with a username
// and password.
func (cmd *Commands) Oper(user, pass string) {
	cmd.c.Send(&Event{Command: OPER, Params: []string{user, pass}, Sensitive: true})
}

// Kick sends a KICK query to the server, attempting to kick nick from
// channel, with reason. If reason is blank, one will not be sent to the
// server.
func (cmd *Commands) Kick(channel, nick, reason string) error {
	if !IsValidChannel(channel) {
		return &ErrInvalidTarget{Target: channel}
	}

	if !IsValidNick(nick) {
		return &ErrInvalidTarget{Target: nick}
	}

	if reason != "" {
		cmd.c.Send(&Event{Command: KICK, Params: []string{channel, nick}, Trailing: reason})
		return nil
	}

	cmd.c.Send(&Event{Command: KICK, Params: []string{channel, nick}})
	return nil
}

// Invite sends a INVITE query to the server, to invite nick to channel.
func (cmd *Commands) Invite(channel, nick string) error {
	if !IsValidChannel(channel) {
		return &ErrInvalidTarget{Target: channel}
	}

	if !IsValidNick(nick) {
		return &ErrInvalidTarget{Target: nick}
	}

	cmd.c.Send(&Event{Command: INVITE, Params: []string{nick, channel}})
	return nil
}

// Away sends a AWAY query to the server, suggesting that the client is no
// longer active. If reason is blank, Client.Back() is called. Also see
// Client.Back().
func (cmd *Commands) Away(reason string) {
	if reason == "" {
		cmd.Back()
		return
	}

	cmd.c.Send(&Event{Command: AWAY, Params: []string{reason}})
}

// Back sends a AWAY query to the server, however the query is blank,
// suggesting that the client is active once again. Also see Client.Away().
func (cmd *Commands) Back() {
	cmd.c.Send(&Event{Command: AWAY})
}

// List sends a LIST query to the server, which will list channels and topics.
// Supports multiple channels at once, in hopes it will reduce extensive
// LIST queries to the server. Supply no channels to run a list against the
// entire server (warning, that may mean LOTS of channels!)
func (cmd *Commands) List(channels ...string) error {
	if len(channels) == 0 {
		cmd.c.Send(&Event{Command: LIST})
		return nil
	}

	// We can LIST multiple channels at once, however we need to ensure that
	// we are not exceeding the line length. (see maxLength)
	max := maxLength - len(JOIN) - 1

	var buffer string

	for i := 0; i < len(channels); i++ {
		if !IsValidChannel(channels[i]) {
			return &ErrInvalidTarget{Target: channels[i]}
		}

		if len(buffer+","+channels[i]) > max {
			cmd.c.Send(&Event{Command: LIST, Params: []string{buffer}})
			buffer = ""
			continue
		}

		if len(buffer) == 0 {
			buffer = channels[i]
		} else {
			buffer += "," + channels[i]
		}

		if i == len(channels)-1 {
			cmd.c.Send(&Event{Command: LIST, Params: []string{buffer}})
			return nil
		}
	}

	return nil
}

// Whowas sends a WHOWAS query to the server. amount is the amount of results
// you want back.
func (cmd *Commands) Whowas(nick string, amount int) error {
	if !IsValidNick(nick) {
		return &ErrInvalidTarget{Target: nick}
	}

	cmd.c.Send(&Event{Command: WHOWAS, Params: []string{nick, string(amount)}})
	return nil
}
<commit_msg>implement Reply, Replyf, ReplyTo and ReplyTof<commit_after>// Copyright (c) Liam Stanley <me@liamstanley.io>. All rights reserved. Use
// of this source code is governed by the MIT license that can be found in
// the LICENSE file.

package girc

import (
	"errors"
	"fmt"
)

// Commands holds a large list of useful methods to interact with the server,
// and wrappers for common events.
type Commands struct {
	c *Client
}

// Nick changes the client nickname.
func (cmd *Commands) Nick(name string) error {
	if !IsValidNick(name) {
		return &ErrInvalidTarget{Target: name}
	}

	cmd.c.Send(&Event{Command: NICK, Params: []string{name}})
	return nil
}

// Join attempts to enter a list of IRC channels, at bulk if possible to
// prevent sending extensive JOIN commands.
func (cmd *Commands) Join(channels ...string) error {
	// We can join multiple channels at once, however we need to ensure that
	// we are not exceeding the line length. (see maxLength)
	max := maxLength - len(JOIN) - 1

	var buffer string

	for i := 0; i < len(channels); i++ {
		if !IsValidChannel(channels[i]) {
			return &ErrInvalidTarget{Target: channels[i]}
		}

		if len(buffer+","+channels[i]) > max {
			cmd.c.Send(&Event{Command: JOIN, Params: []string{buffer}})
			buffer = ""
			continue
		}

		if len(buffer) == 0 {
			buffer = channels[i]
		} else {
			buffer += "," + channels[i]
		}

		if i == len(channels)-1 {
			cmd.c.Send(&Event{Command: JOIN, Params: []string{buffer}})
			return nil
		}
	}

	return nil
}

// JoinKey attempts to enter an IRC channel with a password.
func (cmd *Commands) JoinKey(channel, password string) error {
	if !IsValidChannel(channel) {
		return &ErrInvalidTarget{Target: channel}
	}

	cmd.c.Send(&Event{Command: JOIN, Params: []string{channel, password}})
	return nil
}

// Part leaves an IRC channel.
func (cmd *Commands) Part(channel, message string) error {
	if !IsValidChannel(channel) {
		return &ErrInvalidTarget{Target: channel}
	}

	cmd.c.Send(&Event{Command: JOIN, Params: []string{channel}})
	return nil
}

// PartMessage leaves an IRC channel with a specified leave message.
func (cmd *Commands) PartMessage(channel, message string) error {
	if !IsValidChannel(channel) {
		return &ErrInvalidTarget{Target: channel}
	}

	cmd.c.Send(&Event{Command: JOIN, Params: []string{channel}, Trailing: message})
	return nil
}

// SendCTCP sends a CTCP request to target. Note that this method uses
// PRIVMSG specifically.
func (cmd *Commands) SendCTCP(target, ctcpType, message string) error {
	out := encodeCTCPRaw(ctcpType, message)
	if out == "" {
		return errors.New("invalid CTCP")
	}

	return cmd.Message(target, out)
}

// SendCTCPf sends a CTCP request to target using a specific format. Note that
// this method uses PRIVMSG specifically.
func (cmd *Commands) SendCTCPf(target, ctcpType, format string, a ...interface{}) error {
	return cmd.SendCTCP(target, ctcpType, fmt.Sprintf(format, a...))
}

// SendCTCPReplyf sends a CTCP response to target using a specific format.
// Note that this method uses NOTICE specifically.
func (cmd *Commands) SendCTCPReplyf(target, ctcpType, format string, a ...interface{}) error {
	return cmd.SendCTCPReply(target, ctcpType, fmt.Sprintf(format, a...))
}

// SendCTCPReply sends a CTCP response to target. Note that this method uses
// NOTICE specifically.
func (cmd *Commands) SendCTCPReply(target, ctcpType, message string) error {
	out := encodeCTCPRaw(ctcpType, message)
	if out == "" {
		return errors.New("invalid CTCP")
	}

	return cmd.Notice(target, out)
}

// Message sends a PRIVMSG to target (either channel, service, or user).
func (cmd *Commands) Message(target, message string) error {
	if !IsValidNick(target) && !IsValidChannel(target) {
		return &ErrInvalidTarget{Target: target}
	}

	cmd.c.Send(&Event{Command: PRIVMSG, Params: []string{target}, Trailing: message})
	return nil
}

// Messagef sends a formated PRIVMSG to target (either channel, service, or
// user).
func (cmd *Commands) Messagef(target, format string, a ...interface{}) error {
	return cmd.Message(target, fmt.Sprintf(format, a...))
}

var ErrInvalidSource = errors.New("event has nil or invalid source address")

// Reply sends a reply to channel or user, based on where the supplied event
// originated from. See also ReplyTo().
func (cmd *Commands) Reply(event Event, message string) error {
	if event.Source == nil {
		return ErrInvalidSource
	}

	if len(event.Params) > 0 && IsValidChannel(event.Params[0]) {
		return cmd.Message(event.Params[0], message)
	}

	return cmd.Message(event.Source.Name, message)
}

// Replyf sends a reply to channel or user with a format string, based on
// where the supplied event originated from. See also ReplyTof().
func (cmd *Commands) Replyf(event Event, format string, a ...interface{}) error {
	return cmd.Reply(event, fmt.Sprintf(format, a...))
}

// ReplyTo sends a reply to a channel or user, based on where the supplied
// event originated from. ReplyTo(), when originating from a channel will
// default to replying with "<user>, <message>". See also Reply().
func (cmd *Commands) ReplyTo(event Event, message string) error {
	if event.Source == nil {
		return ErrInvalidSource
	}

	if len(event.Params) > 0 && IsValidChannel(event.Params[0]) {
		return cmd.Message(event.Params[0], event.Source.Name+", "+message)
	}

	return cmd.Message(event.Source.Name, message)
}

// ReplyTof sends a reply to a channel or user with a format string, based
// on where the supplied event originated from. ReplyTo(), when originating
// from a channel will default to replying with "<user>, <message>". See
// also Replyf().
func (cmd *Commands) ReplyTof(event Event, format string, a ...interface{}) error {
	return cmd.ReplyTo(event, fmt.Sprintf(format, a...))
}

// Action sends a PRIVMSG ACTION (/me) to target (either channel, service,
// or user).
func (cmd *Commands) Action(target, message string) error {
	if !IsValidNick(target) && !IsValidChannel(target) {
		return &ErrInvalidTarget{Target: target}
	}

	cmd.c.Send(&Event{
		Command:  PRIVMSG,
		Params:   []string{target},
		Trailing: fmt.Sprintf("\001ACTION %s\001", message),
	})
	return nil
}

// Actionf sends a formated PRIVMSG ACTION (/me) to target (either channel,
// service, or user).
func (cmd *Commands) Actionf(target, format string, a ...interface{}) error {
	return cmd.Action(target, fmt.Sprintf(format, a...))
}

// Notice sends a NOTICE to target (either channel, service, or user).
func (cmd *Commands) Notice(target, message string) error {
	if !IsValidNick(target) && !IsValidChannel(target) {
		return &ErrInvalidTarget{Target: target}
	}

	cmd.c.Send(&Event{Command: NOTICE, Params: []string{target}, Trailing: message})
	return nil
}

// Noticef sends a formated NOTICE to target (either channel, service, or
// user).
func (cmd *Commands) Noticef(target, format string, a ...interface{}) error {
	return cmd.Notice(target, fmt.Sprintf(format, a...))
}

// SendRaw sends a raw string back to the server, without carriage returns
// or newlines.
func (cmd *Commands) SendRaw(raw string) error {
	e := ParseEvent(raw)
	if e == nil {
		return errors.New("invalid event: " + raw)
	}

	cmd.c.Send(e)
	return nil
}

// SendRawf sends a formated string back to the server, without carriage
// returns or newlines.
func (cmd *Commands) SendRawf(format string, a ...interface{}) error {
	return cmd.SendRaw(fmt.Sprintf(format, a...))
}

// Topic sets the topic of channel to message. Does not verify the length
// of the topic.
func (cmd *Commands) Topic(channel, message string) {
	cmd.c.Send(&Event{Command: TOPIC, Params: []string{channel}, Trailing: message})
}

// Who sends a WHO query to the server, which will attempt WHOX by default.
// See http://faerion.sourceforge.net/doc/irc/whox.var for more details. This
// sends "%tcuhnr,2" per default. Do not use "1" as this will conflict with
// girc's builtin tracking functionality.
func (cmd *Commands) Who(target string) error {
	if !IsValidNick(target) && !IsValidChannel(target) && !IsValidUser(target) {
		return &ErrInvalidTarget{Target: target}
	}

	cmd.c.Send(&Event{Command: WHO, Params: []string{target, "%tcuhnr,2"}})
	return nil
}

// Whois sends a WHOIS query to the server, targeted at a specific user.
// as WHOIS is a bit slower, you may want to use WHO for brief user info.
func (cmd *Commands) Whois(nick string) error {
	if !IsValidNick(nick) {
		return &ErrInvalidTarget{Target: nick}
	}

	cmd.c.Send(&Event{Command: WHOIS, Params: []string{nick}})
	return nil
}

// Ping sends a PING query to the server, with a specific identifier that
// the server should respond with.
func (cmd *Commands) Ping(id string) {
	cmd.c.Send(&Event{Command: PING, Params: []string{id}})
}

// Pong sends a PONG query to the server, with an identifier which was
// received from a previous PING query received by the client.
func (cmd *Commands) Pong(id string) {
	cmd.c.Send(&Event{Command: PONG, Params: []string{id}})
}

// Oper sends a OPER authentication query to the server, with a username
// and password.
func (cmd *Commands) Oper(user, pass string) {
	cmd.c.Send(&Event{Command: OPER, Params: []string{user, pass}, Sensitive: true})
}

// Kick sends a KICK query to the server, attempting to kick nick from
// channel, with reason. If reason is blank, one will not be sent to the
// server.
func (cmd *Commands) Kick(channel, nick, reason string) error {
	if !IsValidChannel(channel) {
		return &ErrInvalidTarget{Target: channel}
	}

	if !IsValidNick(nick) {
		return &ErrInvalidTarget{Target: nick}
	}

	if reason != "" {
		cmd.c.Send(&Event{Command: KICK, Params: []string{channel, nick}, Trailing: reason})
		return nil
	}

	cmd.c.Send(&Event{Command: KICK, Params: []string{channel, nick}})
	return nil
}

// Invite sends a INVITE query to the server, to invite nick to channel.
func (cmd *Commands) Invite(channel, nick string) error {
	if !IsValidChannel(channel) {
		return &ErrInvalidTarget{Target: channel}
	}

	if !IsValidNick(nick) {
		return &ErrInvalidTarget{Target: nick}
	}

	cmd.c.Send(&Event{Command: INVITE, Params: []string{nick, channel}})
	return nil
}

// Away sends a AWAY query to the server, suggesting that the client is no
// longer active. If reason is blank, Client.Back() is called. Also see
// Client.Back().
func (cmd *Commands) Away(reason string) {
	if reason == "" {
		cmd.Back()
		return
	}

	cmd.c.Send(&Event{Command: AWAY, Params: []string{reason}})
}

// Back sends a AWAY query to the server, however the query is blank,
// suggesting that the client is active once again. Also see Client.Away().
func (cmd *Commands) Back() {
	cmd.c.Send(&Event{Command: AWAY})
}

// List sends a LIST query to the server, which will list channels and topics.
// Supports multiple channels at once, in hopes it will reduce extensive
// LIST queries to the server. Supply no channels to run a list against the
// entire server (warning, that may mean LOTS of channels!)
func (cmd *Commands) List(channels ...string) error {
	if len(channels) == 0 {
		cmd.c.Send(&Event{Command: LIST})
		return nil
	}

	// We can LIST multiple channels at once, however we need to ensure that
	// we are not exceeding the line length. (see maxLength)
	max := maxLength - len(JOIN) - 1

	var buffer string

	for i := 0; i < len(channels); i++ {
		if !IsValidChannel(channels[i]) {
			return &ErrInvalidTarget{Target: channels[i]}
		}

		if len(buffer+","+channels[i]) > max {
			cmd.c.Send(&Event{Command: LIST, Params: []string{buffer}})
			buffer = ""
			continue
		}

		if len(buffer) == 0 {
			buffer = channels[i]
		} else {
			buffer += "," + channels[i]
		}

		if i == len(channels)-1 {
			cmd.c.Send(&Event{Command: LIST, Params: []string{buffer}})
			return nil
		}
	}

	return nil
}

// Whowas sends a WHOWAS query to the server. amount is the amount of results
// you want back.
func (cmd *Commands) Whowas(nick string, amount int) error {
	if !IsValidNick(nick) {
		return &ErrInvalidTarget{Target: nick}
	}

	cmd.c.Send(&Event{Command: WHOWAS, Params: []string{nick, string(amount)}})
	return nil
}
<|endoftext|>