<commit_before>/*
	Command img2ansi renders raster images for a terminal using ANSI color
	codes.  Supported image types are JPEG, PNG, and GIF (which may be
	animated).

		img2ansi motd.png
		img2ansi -animate -repeat=100 -width=78 https://i.imgur.com/872FDBm.gif
		img2ansi -h

	The command takes as arguments URLs referencing images to render.  If no
	arguments are given img2ansi reads image data from standard input.  Image
	URLs may be local files (simple paths or file:// urls) or HTTP(S) URLs.
*/
package main

import (
	"bufio"
	"bytes"
	"flag"
	"fmt"
	"image"
	"image/color"
	"image/draw"
	"image/gif"
	_ "image/jpeg"
	_ "image/png"
	"io"
	"log"
	"math"
	"net/http"
	"net/url"
	"os"
	"runtime"
	"runtime/pprof"

	"github.com/nfnt/resize"
)

const ANSIClear = "\033[0m"

var AlphaThreshold = uint32(0xffff)

func IsTransparent(c color.Color, threshold uint32) bool {
	_, _, _, a := c.RGBA()
	return a < threshold
}

func init() {
	runtime.GOMAXPROCS(runtime.NumCPU())
	log.SetFlags(0)
}

func main() {
	fopts := new(FrameOptions)

	cpuprofile := flag.String("cpuprofile", "", "path of pprof CPU profile output")
	width := flag.Int("width", 0, "desired width in terminal columns")
	paletteName := flag.String("color", "256", "color palette (8, 256, gray, ...)")
	fontAspect := flag.Float64("fontaspect", 0.5, "aspect ratio (width/height)")
	alphaThreshold := flag.Float64("alphamin", 1.0, "transparency threshold")
	useStdin := flag.Bool("stdin", false, "read image data from stdin")
	flag.StringVar(&fopts.Pad, "pad", " ", "pad output on the left with whitespace")
	flag.BoolVar(&fopts.Animate, "animate", false, "animate images")
	flag.IntVar(&fopts.Repeat, "repeat", 0, "number of animated loops")
	flag.Parse()
	if *useStdin && flag.NArg() > 0 {
		log.Fatal("no arguments are expected when -stdin provided")
	}

	AlphaThreshold = uint32(*alphaThreshold * float64(0xffff))

	palette := ansiPalettes[*paletteName]
	if palette == nil {
		log.Fatalf("color palette not one of %q", ANSIPalettes())
	}

	if *cpuprofile != "" {
		f, err := os.Create(*cpuprofile)
		if err != nil {
			log.Fatal(err)
		}
		pprof.StartCPUProfile(f)
		defer pprof.StopCPUProfile()
	}

	var imgs []image.Image
	var err error
	if *useStdin || flag.NArg() == 0 {
		imgs, err = readFrames(os.Stdin)
	} else {
		for _, filename := range flag.Args() {
			var fimgs []image.Image
			fimgs, err = readFramesURL(filename)
			imgs = append(imgs, fimgs...)
		}
	}
	if err != nil {
		log.Fatalf("image: %v", err)
	}

	// resize img to the proper width and aspect ratio
	for i, img := range imgs {
		size := img.Bounds().Size()
		if *width > 0 {
			size = sizeWidth(size, *width, *fontAspect)
		} else {
			size = sizeNormal(size, *fontAspect)
		}
		if size != img.Bounds().Size() {
			img = resize.Resize(uint(size.X), uint(size.Y), img, 0)
		}
		imgs[i] = img
	}

	err = writeANSIFramePixels(os.Stdout, imgs, palette, fopts)
	if err != nil {
		log.Fatalf("write: %v", err)
	}
}

// FrameOptions describes how to render a sequence of frames in a terminal.
type FrameOptions struct {
	// Pad is a string prepended to each row of pixels.
	Pad string

	// Animate will animate the frames when true.  Animation is accomplished by
	// emitting a control sequence to reset the cursor before rendering each
	// frame.
	Animate bool

	// Repeat specifies the number of times to render the frame sequence.  If
	// Repeat is zero the frames are rendered just once.  If Repeat is less
	// than zero the frames are rendered indefinitely.
	Repeat int
}

func writeANSIFramePixels(w io.Writer, imgs []image.Image, p ANSIPalette, opts *FrameOptions) error {
	var rect image.Rectangle
	animate := opts != nil && opts.Animate

	loopn := 1
	if opts != nil {
		loopn += opts.Repeat
	}

	for loop := 0; loopn <= 0 || loop < loopn; loop++ {
		for _, img := range imgs {
			if animate {
				up := rect.Size().Y
				rect = img.Bounds()
				if up > 0 {
					fmt.Fprintf(w, "\033[%dA", up)
				}
			}
			err := writeANSIPixels(w, img, p, opts.Pad)
			if err != nil {
				return err
			}
		}
	}

	return nil
}

var lineBytes = []byte{'\n'}
var spaceBytes = []byte{' '}

func writeANSIPixels(w io.Writer, img image.Image, p ANSIPalette, pad string) error {
	wbuf := bufio.NewWriter(w)
	writeansii := func() func(color string) {
		var lastcolor string
		return func(color string) {
			if color != lastcolor {
				lastcolor = color
				wbuf.WriteString(color)
			}
		}
	}()
	rect := img.Bounds()
	size := rect.Size()
	for y := 0; y < size.Y; y++ {
		wbuf.WriteString(pad)
		for x := 0; x < size.X; x++ {
			color := img.At(rect.Min.X+x, rect.Min.Y+y)
			writeansii(p.ANSI(color))
			wbuf.Write(spaceBytes)
		}
		wbuf.WriteString(pad)
		writeansii(ANSIClear)
		wbuf.Write(lineBytes)
	}
	return wbuf.Flush()
}

func readFramesURL(urlstr string) ([]image.Image, error) {
	u, err := url.Parse(urlstr)
	if err != nil {
		return nil, err
	}
	if u.Scheme == "" {
		return readFramesFile(urlstr)
	}
	if u.Scheme == "file" {
		return readFramesFile(u.Path)
	}
	if u.Scheme == "http" || u.Scheme == "https" {
		return readFramesHTTP(urlstr)
	}
	return nil, fmt.Errorf("unrecognized url: %v", urlstr)
}

func readFramesHTTP(u string) ([]image.Image, error) {
	resp, err := http.Get(u)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode >= 400 {
		return nil, fmt.Errorf("http: %v %v", resp.Status, u)
	}
	if resp.StatusCode >= 300 {
		// TODO:
		// Handle redirects better
		return nil, fmt.Errorf("http: %v %v", resp.Status, u)
	}
	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("http: %v %v", resp.Status, u)
	}
	switch resp.Header.Get("Content-Type") {
	case "application/octet-stream", "image/png", "image/gif", "image/jpeg":
		return readFrames(resp.Body)
	default:
		return nil, fmt.Errorf("mime: %v %v", resp.Header.Get("Content-Type"), u)
	}
}

func readFramesFile(filename string) ([]image.Image, error) {
	f, err := os.Open(filename)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	return readFrames(f)
}

func readFrames(r io.Reader) ([]image.Image, error) {
	var confbuf bytes.Buffer
	_, format, err := image.DecodeConfig(io.TeeReader(r, &confbuf))
	if err != nil {
		return nil, err
	}
	r = io.MultiReader(&confbuf, r)
	if format == "gif" {
		return readFramesGIF(r)
	}
	img, _, err := image.Decode(r)
	if err != nil {
		return nil, err
	}
	return []image.Image{img}, nil
}

func readFramesGIF(r io.Reader) ([]image.Image, error) {
	img, err := gif.DecodeAll(r)
	if err != nil {
		return nil, err
	}
	return framesGIF(img), nil
}

// framesGIF computes the raw frames of g by successively applying layers.
func framesGIF(g *gif.GIF) []image.Image {
	if len(g.Image) == 0 {
		return nil
	}

	// determine the overall dimensions of the image.
	var imgs []image.Image
	rect := g.Image[0].Rect
	for _, layer := range g.Image {
		r := layer.Bounds()
		if r.Min.X < rect.Min.X {
			rect.Min.X = r.Min.X
		}
		if r.Min.Y < rect.Min.Y {
			rect.Min.Y = r.Min.Y
		}
		if r.Max.X > rect.Max.X {
			rect.Max.X = r.Max.X
		}
		if r.Max.Y > rect.Max.Y {
			rect.Max.Y = r.Max.Y
		}
	}

	// draw each frame within the larger rectangle
	for _, img := range g.Image {
		frame := image.NewRGBA64(rect)
		r := img.Bounds()
		draw.Draw(frame, r, img, r.Min, draw.Over)
		imgs = append(imgs, frame)
	}

	return imgs
}

// readImage reads an image.Image from a specified file.
func readImage(filename string) (image.Image, string, error) {
	f, err := os.Open(filename)
	if err != nil {
		return nil, "", err
	}
	defer f.Close()

	img, format, err := image.Decode(f)
	if err != nil {
		return nil, "", err
	}
	return img, format, nil
}

// sizeWidth returns a point with X equal to width and the same normalized
// aspect ratio as size.
func sizeWidth(size image.Point, width int, fontAspect float64) image.Point {
	size = sizeNormal(size, fontAspect)
	aspect := float64(size.X) / float64(size.Y)
	size.X = width
	size.Y = int(round(float64(width) / aspect))
	return size
}

// sizeNormal scales size according to aspect ratio fontAspect and returns the
// new size.
func sizeNormal(size image.Point, fontAspect float64) image.Point {
	aspect := float64(size.X) / float64(size.Y)
	norm := size
	norm.Y = size.Y
	w := float64(norm.Y) * aspect / fontAspect
	norm.X = int(round(w))
	return norm
}

// round x to the nearest integer biased toward +Inf.
func round(x float64) float64 {
	return math.Floor(x + 0.5)
}
<commit_msg>detect dimensions to scale terminal with the flag "-scale"<commit_after>/*
	Command img2ansi renders raster images for a terminal using ANSI color
	codes.  Supported image types are JPEG, PNG, and GIF (which may be
	animated).

		img2ansi motd.png
		img2ansi -animate -repeat=100 -width=78 https://i.imgur.com/872FDBm.gif
		img2ansi -h

	The command takes as arguments URLs referencing images to render.  If no
	arguments are given img2ansi reads image data from standard input.  Image
	URLs may be local files (simple paths or file:// urls) or HTTP(S) URLs.
*/
package main

import (
	"bufio"
	"bytes"
	"flag"
	"fmt"
	"image"
	"image/color"
	"image/draw"
	"image/gif"
	_ "image/jpeg"
	_ "image/png"
	"io"
	"log"
	"math"
	"net/http"
	"net/url"
	"os"
	"runtime"
	"runtime/pprof"
	"time"

	"github.com/nfnt/resize"
)

const ANSIClear = "\033[0m"

var AlphaThreshold = uint32(0xffff)

func IsTransparent(c color.Color, threshold uint32) bool {
	_, _, _, a := c.RGBA()
	return a < threshold
}

func init() {
	runtime.GOMAXPROCS(runtime.NumCPU())
	log.SetFlags(0)
}

func main() {
	fopts := new(FrameOptions)

	cpuprofile := flag.String("cpuprofile", "", "path of pprof CPU profile output")
	scaleToTerm := flag.Bool("scale", false, "scale the image to fit the current terminal")
	width := flag.Int("width", 0, "desired width in terminal columns")
	paletteName := flag.String("color", "256", "color palette (8, 256, gray, ...)")
	fontAspect := flag.Float64("fontaspect", 0.5, "aspect ratio (width/height)")
	alphaThreshold := flag.Float64("alphamin", 1.0, "transparency threshold")
	useStdin := flag.Bool("stdin", false, "read image data from stdin")
	flag.StringVar(&fopts.Pad, "pad", " ", "pad output on the left with whitespace")
	flag.BoolVar(&fopts.Animate, "animate", false, "animate images")
	flag.IntVar(&fopts.Repeat, "repeat", 0, "number of animated loops")
	flag.Parse()
	if *useStdin && flag.NArg() > 0 {
		log.Fatal("no arguments are expected when -stdin provided")
	}

	AlphaThreshold = uint32(*alphaThreshold * float64(0xffff))

	palette := ansiPalettes[*paletteName]
	if palette == nil {
		log.Fatalf("color palette not one of %q", ANSIPalettes())
	}

	if *cpuprofile != "" {
		f, err := os.Create(*cpuprofile)
		if err != nil {
			log.Fatal(err)
		}
		pprof.StartCPUProfile(f)
		defer pprof.StopCPUProfile()
	}

	var imgs []image.Image
	var err error
	if *useStdin || flag.NArg() == 0 {
		imgs, err = readFrames(os.Stdin)
	} else {
		for _, filename := range flag.Args() {
			var fimgs []image.Image
			fimgs, err = readFramesURL(filename)
			imgs = append(imgs, fimgs...)
		}
	}
	if err != nil {
		log.Fatalf("image: %v", err)
	}

	// resize the images to the proper size and aspect ratio
	for i, img := range imgs {
		size := img.Bounds().Size()
		if *scaleToTerm {
			w, h, err := getTermDim()
			if err != nil {
				log.Fatal(err)
			}

			// correct for wrap/overflow due to newlines/padding.
			// BUG:
			// scaled height is reduced because a newline follwing the final
			// line does bad things for animation.
			w -= len(fopts.Pad)
			h -= 1

			if w > h {
				log.Print(w, h)
				size = sizeHeight(size, h, *fontAspect)
				log.Print(size)
			} else {
				size = sizeWidth(size, w, *fontAspect)
			}
		} else if *width > 0 {
			size = sizeWidth(size, *width, *fontAspect)
		} else {
			size = sizeNormal(size, *fontAspect)
		}
		if size != img.Bounds().Size() {
			img = resize.Resize(uint(size.X), uint(size.Y), img, 0)
		}
		imgs[i] = img
	}

	err = writeANSIFramePixels(os.Stdout, imgs, palette, fopts)
	if err != nil {
		log.Fatalf("write: %v", err)
	}
}

// FrameOptions describes how to render a sequence of frames in a terminal.
type FrameOptions struct {
	// Pad is a string prepended to each row of pixels.
	Pad string

	// Animate will animate the frames when true.  Animation is accomplished by
	// emitting a control sequence to reset the cursor before rendering each
	// frame.
	Animate bool

	// Repeat specifies the number of times to render the frame sequence.  If
	// Repeat is zero the frames are rendered just once.  If Repeat is less
	// than zero the frames are rendered indefinitely.
	Repeat int
}

func writeANSIFramePixels(w io.Writer, imgs []image.Image, p ANSIPalette, opts *FrameOptions) error {
	var rect image.Rectangle
	animate := opts != nil && opts.Animate

	loopn := 1
	if opts != nil {
		loopn += opts.Repeat
	}

	for loop := 0; loopn <= 0 || loop < loopn; loop++ {
		for _, img := range imgs {
			if animate {
				up := rect.Size().Y
				rect = img.Bounds()
				if up > 0 {
					fmt.Fprintf(w, "\033[%dA", up)
				}
			}
			err := writeANSIPixels(w, img, p, opts.Pad)
			if err != nil {
				return err
			}
		}
	}

	return nil
}

func writeANSIPixels(w io.Writer, img image.Image, p ANSIPalette, pad string) error {
	wbuf := bufio.NewWriter(w)
	writeansii := func() func(color string) {
		var lastcolor string
		return func(color string) {
			if color != lastcolor {
				lastcolor = color
				wbuf.WriteString(color)
			}
		}
	}()
	rect := img.Bounds()
	size := rect.Size()
	for y := 0; y < size.Y; y++ {
		wbuf.WriteString(pad)
		for x := 0; x < size.X; x++ {
			color := img.At(rect.Min.X+x, rect.Min.Y+y)
			writeansii(p.ANSI(color))
			wbuf.WriteString(" ")
		}
		wbuf.WriteString(pad)
		writeansii(ANSIClear)
		wbuf.WriteString("\n")
	}
	return wbuf.Flush()
}

func readFramesURL(urlstr string) ([]image.Image, error) {
	u, err := url.Parse(urlstr)
	if err != nil {
		return nil, err
	}
	if u.Scheme == "" {
		return readFramesFile(urlstr)
	}
	if u.Scheme == "file" {
		return readFramesFile(u.Path)
	}
	if u.Scheme == "http" || u.Scheme == "https" {
		return readFramesHTTP(urlstr)
	}
	return nil, fmt.Errorf("unrecognized url: %v", urlstr)
}

func readFramesHTTP(u string) ([]image.Image, error) {
	client := http.Client{
		Timeout: 10 * time.Second,
	}
	resp, err := client.Get(u)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode >= 400 {
		return nil, fmt.Errorf("http: %v %v", resp.Status, u)
	}
	if resp.StatusCode >= 300 {
		// TODO:
		// Handle redirects better
		return nil, fmt.Errorf("http: %v %v", resp.Status, u)
	}
	if resp.StatusCode != 200 {
		return nil, fmt.Errorf("http: %v %v", resp.Status, u)
	}
	switch resp.Header.Get("Content-Type") {
	case "application/octet-stream", "image/png", "image/gif", "image/jpeg":
		return readFrames(resp.Body)
	default:
		return nil, fmt.Errorf("mime: %v %v", resp.Header.Get("Content-Type"), u)
	}
}

func readFramesFile(filename string) ([]image.Image, error) {
	f, err := os.Open(filename)
	if err != nil {
		return nil, err
	}
	defer f.Close()
	return readFrames(f)
}

func readFrames(r io.Reader) ([]image.Image, error) {
	var confbuf bytes.Buffer
	_, format, err := image.DecodeConfig(io.TeeReader(r, &confbuf))
	if err != nil {
		return nil, err
	}
	r = io.MultiReader(&confbuf, r)
	if format == "gif" {
		return readFramesGIF(r)
	}
	img, _, err := image.Decode(r)
	if err != nil {
		return nil, err
	}
	return []image.Image{img}, nil
}

func readFramesGIF(r io.Reader) ([]image.Image, error) {
	img, err := gif.DecodeAll(r)
	if err != nil {
		return nil, err
	}
	return framesGIF(img), nil
}

// framesGIF computes the raw frames of g by successively applying layers.
func framesGIF(g *gif.GIF) []image.Image {
	if len(g.Image) == 0 {
		return nil
	}

	// determine the overall dimensions of the image.
	var imgs []image.Image
	rect := g.Image[0].Rect
	for _, layer := range g.Image {
		r := layer.Bounds()
		if r.Min.X < rect.Min.X {
			rect.Min.X = r.Min.X
		}
		if r.Min.Y < rect.Min.Y {
			rect.Min.Y = r.Min.Y
		}
		if r.Max.X > rect.Max.X {
			rect.Max.X = r.Max.X
		}
		if r.Max.Y > rect.Max.Y {
			rect.Max.Y = r.Max.Y
		}
	}

	// draw each frame within the larger rectangle
	for _, img := range g.Image {
		frame := image.NewRGBA64(rect)
		r := img.Bounds()
		draw.Draw(frame, r, img, r.Min, draw.Over)
		imgs = append(imgs, frame)
	}

	return imgs
}

// readImage reads an image.Image from a specified file.
func readImage(filename string) (image.Image, string, error) {
	f, err := os.Open(filename)
	if err != nil {
		return nil, "", err
	}
	defer f.Close()

	img, format, err := image.Decode(f)
	if err != nil {
		return nil, "", err
	}
	return img, format, nil
}

// sizeWidth returns a point with X equal to width and the same normalized
// aspect ratio as size.
func sizeWidth(size image.Point, width int, fontAspect float64) image.Point {
	size = sizeNormal(size, fontAspect)
	aspect := float64(size.X) / float64(size.Y)
	size.X = width
	size.Y = int(round(float64(width) / aspect))
	return size
}

// sizeHeight returns a point with Y equal to height and the same normalized
// aspect ratio as size.
func sizeHeight(size image.Point, height int, fontAspect float64) image.Point {
	size = sizeNormal(size, fontAspect)
	aspect := float64(size.X) / float64(size.Y)
	size.Y = height
	size.X = int(round(float64(height) * aspect))
	return size
}

// sizeNormal scales size according to aspect ratio fontAspect and returns the
// new size.
func sizeNormal(size image.Point, fontAspect float64) image.Point {
	aspect := float64(size.X) / float64(size.Y)
	norm := size
	norm.Y = size.Y
	w := float64(norm.Y) * aspect / fontAspect
	norm.X = int(round(w))
	return norm
}

// round x to the nearest integer biased toward +Inf.
func round(x float64) float64 {
	return math.Floor(x + 0.5)
}
<|endoftext|>


<commit_before>package mesoslib

import (
	"code.google.com/p/goprotobuf/proto"
	"github.com/Sirupsen/logrus"
	"github.com/VoltFramework/volt/mesosproto"
)

func (m *MesosLib) RequestOffer(cpus, mem, disk float64) (*mesosproto.Offer, []*mesosproto.Resource, error) {
	m.Log.WithFields(logrus.Fields{"cpus": cpus, "mem": mem, "disk": disk}).Info("Requesting offers...")

	var (
		resources = []*mesosproto.Resource{
			&mesosproto.Resource{
				Name:   proto.String("cpus"),
				Type:   mesosproto.Value_SCALAR.Enum(),
				Scalar: &mesosproto.Value_Scalar{Value: &cpus},
			},
			&mesosproto.Resource{
				Name:   proto.String("mem"),
				Type:   mesosproto.Value_SCALAR.Enum(),
				Scalar: &mesosproto.Value_Scalar{Value: &mem},
			},
		}
		event *mesosproto.Event
	)

	if disk > 0 {
		resources = append(resources, &mesosproto.Resource{
			Name:   proto.String("disk"),
			Type:   mesosproto.Value_SCALAR.Enum(),
			Scalar: &mesosproto.Value_Scalar{Value: &disk},
		})
	}

	select {
	case event = <-m.GetEvent(mesosproto.Event_OFFERS):
	}

	if event == nil {
		if err := m.send(&mesosproto.ResourceRequestMessage{
			FrameworkId: m.frameworkInfo.Id,
			Requests: []*mesosproto.Request{
				&mesosproto.Request{
					Resources: resources,
				},
			},
		}, "mesos.internal.ResourceRequestMessage"); err != nil {
			return nil, nil, err
		}

		event = <-m.GetEvent(mesosproto.Event_OFFERS)
	}

	if len(event.Offers.Offers) > 0 {
		m.Log.WithFields(logrus.Fields{"Id": event.Offers.Offers[0].Id}).Info("Received offer.")
		return event.Offers.Offers[0], resources, nil
	}
	return nil, nil, nil
}
<commit_msg>any resource can be optional<commit_after>package mesoslib

import (
	"code.google.com/p/goprotobuf/proto"
	"github.com/Sirupsen/logrus"
	"github.com/VoltFramework/volt/mesosproto"
)

func (m *MesosLib) RequestOffer(cpus, mem, disk float64) (*mesosproto.Offer, []*mesosproto.Resource, error) {
	m.Log.WithFields(logrus.Fields{"cpus": cpus, "mem": mem, "disk": disk}).Info("Requesting offers...")

	var (
		resources = []*mesosproto.Resource{}
		event     *mesosproto.Event
	)

	if cpus > 0 {
		resources = append(resources, &mesosproto.Resource{
			Name:   proto.String("cpus"),
			Type:   mesosproto.Value_SCALAR.Enum(),
			Scalar: &mesosproto.Value_Scalar{Value: &cpus},
		})
	}

	if mem > 0 {
		resources = append(resources, &mesosproto.Resource{
			Name:   proto.String("mem"),
			Type:   mesosproto.Value_SCALAR.Enum(),
			Scalar: &mesosproto.Value_Scalar{Value: &mem},
		})
	}

	if disk > 0 {
		resources = append(resources, &mesosproto.Resource{
			Name:   proto.String("disk"),
			Type:   mesosproto.Value_SCALAR.Enum(),
			Scalar: &mesosproto.Value_Scalar{Value: &disk},
		})
	}

	select {
	case event = <-m.GetEvent(mesosproto.Event_OFFERS):
	}

	if event == nil {
		if err := m.send(&mesosproto.ResourceRequestMessage{
			FrameworkId: m.frameworkInfo.Id,
			Requests: []*mesosproto.Request{
				&mesosproto.Request{
					Resources: resources,
				},
			},
		}, "mesos.internal.ResourceRequestMessage"); err != nil {
			return nil, nil, err
		}

		event = <-m.GetEvent(mesosproto.Event_OFFERS)
	}

	if len(event.Offers.Offers) > 0 {
		m.Log.WithFields(logrus.Fields{"Id": event.Offers.Offers[0].Id}).Info("Received offer.")
		return event.Offers.Offers[0], resources, nil
	}
	return nil, nil, nil
}
<|endoftext|>


<commit_before>package stats

import (
	"bufio"
	"io"
	"net/url"
	"time"

	log "github.com/Sirupsen/logrus"

	"github.com/docker/engine-api/client"
	"github.com/docker/engine-api/types"
	"github.com/rancher/host-api/config"
	"github.com/rancher/websocket-proxy/backend"
	"github.com/rancher/websocket-proxy/common"
	"golang.org/x/net/context"
)

type ContainerStatsHandler struct {
}

func (s *ContainerStatsHandler) Handle(key string, initialMessage string, incomingMessages <-chan string, response chan<- common.Message) {
	defer backend.SignalHandlerClosed(key, response)

	requestUrl, err := url.Parse(initialMessage)
	if err != nil {
		log.WithFields(log.Fields{"error": err, "message": initialMessage}).Error("Couldn't parse url from message.")
		return
	}

	tokenString := requestUrl.Query().Get("token")

	containerIds := map[string]string{}

	token, err := parseRequestToken(tokenString, config.Config.ParsedPublicKey)
	if err == nil {
		containerIdsInterface, found := token.Claims["containerIds"]
		if found {
			containerIdsVal, ok := containerIdsInterface.(map[string]interface{})
			if ok {
				for key, val := range containerIdsVal {
					if containerIdsValString, ok := val.(string); ok {
						containerIds[key] = containerIdsValString
					}
				}
			}
		}
	}

	id := ""
	parts := pathParts(requestUrl.Path)
	if len(parts) == 3 {
		id = parts[2]
	}

	if err != nil {
		log.WithFields(log.Fields{"id": id, "error": err}).Error("Couldn't find container for id.")
		return
	}

	dclient, err := client.NewEnvClient()
	if err != nil {
		log.WithFields(log.Fields{"error": err}).Error("Couldn't get docker client.")
		return
	}
	dclient.UpdateClientVersion("1.22")

	reader, writer := io.Pipe()

	go func(w *io.PipeWriter) {
		for {
			_, ok := <-incomingMessages
			if !ok {
				w.Close()
				return
			}
		}
	}(writer)

	go func(r *io.PipeReader) {
		scanner := bufio.NewScanner(r)
		for scanner.Scan() {
			text := scanner.Text()
			message := common.Message{
				Key:  key,
				Type: common.Body,
				Body: text,
			}
			response <- message
		}
		if err := scanner.Err(); err != nil {
			log.WithFields(log.Fields{"error": err}).Error("Error with the container stat scanner.")
		}
	}(reader)

	count := 1
	memLimit, err := getMemCapcity()
	if err != nil {
		log.WithFields(log.Fields{"error": err, "id": id}).Error("Error getting memory capacity.")
		return
	}

	// get single container stats
	if id != "" {
		statsReader, err := dclient.ContainerStats(context.Background(), id, true)
		defer statsReader.Close()
		if err != nil {
			log.WithFields(log.Fields{"error": err}).Error("Can not get stats reader from docker")
			return
		}
		bufioReader := bufio.NewReader(statsReader)
		for {
			infos := []containerInfo{}
			cInfo, err := getContainerStats(bufioReader, count, id)

			if err != nil {
				log.WithFields(log.Fields{"error": err, "id": id}).Error("Error getting container info.")
				return
			}
			infos = append(infos, cInfo)
			for i := range infos {
				if len(infos[i].Stats) > 0 {
					infos[i].Stats[0].Timestamp = time.Now()
				}
			}

			err = writeAggregatedStats(id, containerIds, "container", infos, uint64(memLimit), writer)
			if err != nil {
				return
			}

			time.Sleep(1 * time.Second)
			count = 1
		}
	} else {
		contList, err := dclient.ContainerList(context.Background(), types.ContainerListOptions{})
		if err != nil {
			log.WithFields(log.Fields{"error": err}).Error("Can not list containers")
			return
		}
		IDList := []string{}
		bufioReaders := []*bufio.Reader{}
		for i := 0; i < len(contList); i++ {
			statsReader, err := dclient.ContainerStats(context.Background(), contList[i].ID, true)
			defer statsReader.Close()
			if err != nil {
				log.WithFields(log.Fields{"error": err}).Error("Can not get stats reader from docker")
				return
			}
			bufioReader := bufio.NewReader(statsReader)
			bufioReaders = append(bufioReaders, bufioReader)
			IDList = append(IDList, contList[i].ID)
		}
		for {
			infos := []containerInfo{}
			allInfos, err := getAllDockerContainers(bufioReaders, count, IDList)
			if err != nil {
				log.WithFields(log.Fields{"error": err}).Error("Error getting all container info.")
				return
			}
			infos = append(infos, allInfos...)
			for i := range infos {
				if len(infos[i].Stats) > 0 {
					infos[i].Stats[0].Timestamp = time.Now()
				}
			}
			err = writeAggregatedStats(id, containerIds, "container", infos, uint64(memLimit), writer)
			if err != nil {
				return
			}

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

	return
}
<commit_msg>return stats with only requested id<commit_after>package stats

import (
	"bufio"
	"io"
	"net/url"
	"time"

	log "github.com/Sirupsen/logrus"

	"github.com/docker/engine-api/client"
	"github.com/docker/engine-api/types"
	"github.com/rancher/host-api/config"
	"github.com/rancher/websocket-proxy/backend"
	"github.com/rancher/websocket-proxy/common"
	"golang.org/x/net/context"
)

type ContainerStatsHandler struct {
}

func (s *ContainerStatsHandler) Handle(key string, initialMessage string, incomingMessages <-chan string, response chan<- common.Message) {
	defer backend.SignalHandlerClosed(key, response)

	requestUrl, err := url.Parse(initialMessage)
	if err != nil {
		log.WithFields(log.Fields{"error": err, "message": initialMessage}).Error("Couldn't parse url from message.")
		return
	}

	tokenString := requestUrl.Query().Get("token")

	containerIds := map[string]string{}

	token, err := parseRequestToken(tokenString, config.Config.ParsedPublicKey)
	if err == nil {
		containerIdsInterface, found := token.Claims["containerIds"]
		if found {
			containerIdsVal, ok := containerIdsInterface.(map[string]interface{})
			if ok {
				for key, val := range containerIdsVal {
					if containerIdsValString, ok := val.(string); ok {
						containerIds[key] = containerIdsValString
					}
				}
			}
		}
	}

	id := ""
	parts := pathParts(requestUrl.Path)
	if len(parts) == 3 {
		id = parts[2]
	}

	if err != nil {
		log.WithFields(log.Fields{"id": id, "error": err}).Error("Couldn't find container for id.")
		return
	}

	dclient, err := client.NewEnvClient()
	if err != nil {
		log.WithFields(log.Fields{"error": err}).Error("Couldn't get docker client.")
		return
	}
	dclient.UpdateClientVersion("1.22")

	reader, writer := io.Pipe()

	go func(w *io.PipeWriter) {
		for {
			_, ok := <-incomingMessages
			if !ok {
				w.Close()
				return
			}
		}
	}(writer)

	go func(r *io.PipeReader) {
		scanner := bufio.NewScanner(r)
		for scanner.Scan() {
			text := scanner.Text()
			message := common.Message{
				Key:  key,
				Type: common.Body,
				Body: text,
			}
			response <- message
		}
		if err := scanner.Err(); err != nil {
			log.WithFields(log.Fields{"error": err}).Error("Error with the container stat scanner.")
		}
	}(reader)

	count := 1
	memLimit, err := getMemCapcity()
	if err != nil {
		log.WithFields(log.Fields{"error": err, "id": id}).Error("Error getting memory capacity.")
		return
	}

	// get single container stats
	if id != "" {
		statsReader, err := dclient.ContainerStats(context.Background(), id, true)
		defer statsReader.Close()
		if err != nil {
			log.WithFields(log.Fields{"error": err}).Error("Can not get stats reader from docker")
			return
		}
		bufioReader := bufio.NewReader(statsReader)
		for {
			infos := []containerInfo{}
			cInfo, err := getContainerStats(bufioReader, count, id)

			if err != nil {
				log.WithFields(log.Fields{"error": err, "id": id}).Error("Error getting container info.")
				return
			}
			infos = append(infos, cInfo)
			for i := range infos {
				if len(infos[i].Stats) > 0 {
					infos[i].Stats[0].Timestamp = time.Now()
				}
			}

			err = writeAggregatedStats(id, containerIds, "container", infos, uint64(memLimit), writer)
			if err != nil {
				return
			}

			time.Sleep(1 * time.Second)
			count = 1
		}
	} else {
		contList, err := dclient.ContainerList(context.Background(), types.ContainerListOptions{})
		if err != nil {
			log.WithFields(log.Fields{"error": err}).Error("Can not list containers")
			return
		}
		IDList := []string{}
		bufioReaders := []*bufio.Reader{}
		for _, cont := range contList {
			if _, ok := containerIds[cont.ID]; ok {
				statsReader, err := dclient.ContainerStats(context.Background(), cont.ID, true)
				defer statsReader.Close()
				if err != nil {
					log.WithFields(log.Fields{"error": err}).Error("Can not get stats reader from docker")
					return
				}
				bufioReader := bufio.NewReader(statsReader)
				bufioReaders = append(bufioReaders, bufioReader)
				IDList = append(IDList, cont.ID)
			}
		}
		for {
			infos := []containerInfo{}
			allInfos, err := getAllDockerContainers(bufioReaders, count, IDList)
			if err != nil {
				log.WithFields(log.Fields{"error": err}).Error("Error getting all container info.")
				return
			}
			infos = append(infos, allInfos...)
			for i := range infos {
				if len(infos[i].Stats) > 0 {
					infos[i].Stats[0].Timestamp = time.Now()
				}
			}
			err = writeAggregatedStats(id, containerIds, "container", infos, uint64(memLimit), writer)
			if err != nil {
				return
			}

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

	return
}
<|endoftext|>


<commit_before>package storage

import (
	"database/sql"
	"fmt"
	"strings"
	"time"

	"github.com/mailslurper/libmailslurper/model/attachment"
	"github.com/mailslurper/libmailslurper/model/mailitem"
	"github.com/mailslurper/libmailslurper/model/search"
)

func getMailAndAttachmentsQuery(whereClause string) string {
	sqlQuery := `
		SELECT
			  mailitem.dateSent
			, mailitem.fromAddress
			, mailitem.toAddressList
			, mailitem.subject
			, mailitem.xmailer
			, mailitem.body
			, mailitem.contentType
			, mailitem.boundary
			, attachment.id AS attachmentID
			, attachment.fileName
			, attachment.contentType

		FROM mailitem
			LEFT JOIN attachment ON attachment.mailItemID=mailitem.id

		WHERE 1=1 `

	sqlQuery = sqlQuery + whereClause
	sqlQuery = sqlQuery + ` ORDER BY mailitem.dateSent DESC `

	return sqlQuery
}

func getMailCountQuery(mailSearch *search.MailSearch) (string, []interface{}) {
	sqlQuery := `
		SELECT COUNT(id) AS mailItemCount FROM mailitem WHERE 1=1
	`

	var parameters []interface{}
	return addSearchCriteria(sqlQuery, parameters, mailSearch)
}

func getDeleteMailQuery(startDate string) string {
	where := ""

	if len(startDate) > 0 {
		where = where + " AND dateSent <= ? "
	}

	sqlQuery := "DELETE FROM mailitem WHERE 1=1" + where
	return sqlQuery
}

func getInsertMailQuery() string {
	sqlQuery := `
		INSERT INTO mailitem (
			  id
			, dateSent
			, fromAddress
			, toAddressList
			, subject
			, xmailer
			, body
			, contentType
			, boundary
		) VALUES (
			  ?
			, ?
			, ?
			, ?
			, ?
			, ?
			, ?
			, ?
			, ?
		)
	`

	return sqlQuery
}

func getInsertAttachmentQuery() string {
	sqlQuery := `
		INSERT INTO attachment (
			  id
			, mailItemId
			, fileName
			, contentType
			, content
		) VALUES (
			  ?
			, ?
			, ?
			, ?
			, ?
		)
	`

	return sqlQuery
}

func addOrderBy(sqlQuery string, tablePrefix string, mailSearch *search.MailSearch) string {
	switch mailSearch.OrderByField {
	case "subject":
		sqlQuery += fmt.Sprintf(" ORDER BY %s.subject ", tablePrefix)

	case "from":
		sqlQuery += fmt.Sprintf(" ORDER BY %s.fromAddress ", tablePrefix)

	default:
		sqlQuery += fmt.Sprintf(" ORDER BY %s.dateSent ", tablePrefix)
	}

	switch mailSearch.OrderByDirection {
	case "asc":
		sqlQuery += " ASC "

	default:
		sqlQuery += " DESC "
	}

	return sqlQuery
}

func addSearchCriteria(sqlQuery string, parameters []interface{}, mailSearch *search.MailSearch) (string, []interface{}) {
	var date time.Time
	var err error

	if len(strings.TrimSpace(mailSearch.Message)) > 0 {
		sqlQuery += `
			AND (
				mailitem.body LIKE ?
				OR mailitem.subject LIKE ?
			)
		`

		parameters = append(parameters, "%"+mailSearch.Message+"%")
		parameters = append(parameters, "%"+mailSearch.Message+"%")
	}

	if len(strings.TrimSpace(mailSearch.From)) > 0 {
		sqlQuery += `
			AND mailitem.fromAddress LIKE ?
		`

		parameters = append(parameters, "%"+mailSearch.From+"%")
	}

	if len(strings.TrimSpace(mailSearch.To)) > 0 {
		sqlQuery += `
			AND mailitem.toAddressList LIKE ?
		`

		parameters = append(parameters, "%"+mailSearch.To+"%")
	}

	if len(strings.TrimSpace(mailSearch.Start)) > 0 {
		if date, err = time.Parse("2006-01-02", mailSearch.Start); err == nil {
			sqlQuery += `
				AND mailitem.dateSent >= ?
			`

			parameters = append(parameters, date)
		}
	}

	if len(strings.TrimSpace(mailSearch.End)) > 0 {
		if date, err = time.Parse("2006-01-02", mailSearch.End); err == nil {
			sqlQuery += `
				AND mailitem.dateSent <= ?
			`

			parameters = append(parameters, date)
		}
	}

	return sqlQuery, parameters
}

func storeAttachments(mailItemID string, transaction *sql.Tx, attachments []*attachment.Attachment) error {
	var err error
	var attachmentID string

	for _, currentAttachment := range attachments {
		if attachmentID, err = mailitem.GenerateId(); err != nil {
			return fmt.Errorf("Error generating ID for attachment: %s", err.Error())
		}

		statement, err := transaction.Prepare(getInsertAttachmentQuery())
		if err != nil {
			return fmt.Errorf("Error preparing insert attachment statement: %s", err.Error())
		}

		_, err = statement.Exec(
			attachmentID,
			mailItemID,
			currentAttachment.Headers.FileName,
			currentAttachment.Headers.ContentType,
			currentAttachment.Contents,
		)

		if err != nil {
			return fmt.Errorf("Error executing insert attachment in StoreMail: %s", err.Error())
		}

		statement.Close()
		currentAttachment.Id = attachmentID
	}

	return nil
}
<commit_msg>Checking for less then tomorrow. refs #46<commit_after>package storage

import (
	"database/sql"
	"fmt"
	"strings"
	"time"

	"github.com/mailslurper/libmailslurper/model/attachment"
	"github.com/mailslurper/libmailslurper/model/mailitem"
	"github.com/mailslurper/libmailslurper/model/search"
)

func getMailAndAttachmentsQuery(whereClause string) string {
	sqlQuery := `
		SELECT
			  mailitem.dateSent
			, mailitem.fromAddress
			, mailitem.toAddressList
			, mailitem.subject
			, mailitem.xmailer
			, mailitem.body
			, mailitem.contentType
			, mailitem.boundary
			, attachment.id AS attachmentID
			, attachment.fileName
			, attachment.contentType

		FROM mailitem
			LEFT JOIN attachment ON attachment.mailItemID=mailitem.id

		WHERE 1=1 `

	sqlQuery = sqlQuery + whereClause
	sqlQuery = sqlQuery + ` ORDER BY mailitem.dateSent DESC `

	return sqlQuery
}

func getMailCountQuery(mailSearch *search.MailSearch) (string, []interface{}) {
	sqlQuery := `
		SELECT COUNT(id) AS mailItemCount FROM mailitem WHERE 1=1
	`

	var parameters []interface{}
	return addSearchCriteria(sqlQuery, parameters, mailSearch)
}

func getDeleteMailQuery(startDate string) string {
	where := ""

	if len(startDate) > 0 {
		where = where + " AND dateSent <= ? "
	}

	sqlQuery := "DELETE FROM mailitem WHERE 1=1" + where
	return sqlQuery
}

func getInsertMailQuery() string {
	sqlQuery := `
		INSERT INTO mailitem (
			  id
			, dateSent
			, fromAddress
			, toAddressList
			, subject
			, xmailer
			, body
			, contentType
			, boundary
		) VALUES (
			  ?
			, ?
			, ?
			, ?
			, ?
			, ?
			, ?
			, ?
			, ?
		)
	`

	return sqlQuery
}

func getInsertAttachmentQuery() string {
	sqlQuery := `
		INSERT INTO attachment (
			  id
			, mailItemId
			, fileName
			, contentType
			, content
		) VALUES (
			  ?
			, ?
			, ?
			, ?
			, ?
		)
	`

	return sqlQuery
}

func addOrderBy(sqlQuery string, tablePrefix string, mailSearch *search.MailSearch) string {
	switch mailSearch.OrderByField {
	case "subject":
		sqlQuery += fmt.Sprintf(" ORDER BY %s.subject ", tablePrefix)

	case "from":
		sqlQuery += fmt.Sprintf(" ORDER BY %s.fromAddress ", tablePrefix)

	default:
		sqlQuery += fmt.Sprintf(" ORDER BY %s.dateSent ", tablePrefix)
	}

	switch mailSearch.OrderByDirection {
	case "asc":
		sqlQuery += " ASC "

	default:
		sqlQuery += " DESC "
	}

	return sqlQuery
}

func addSearchCriteria(sqlQuery string, parameters []interface{}, mailSearch *search.MailSearch) (string, []interface{}) {
	var date time.Time
	var err error

	if len(strings.TrimSpace(mailSearch.Message)) > 0 {
		sqlQuery += `
			AND (
				mailitem.body LIKE ?
				OR mailitem.subject LIKE ?
			)
		`

		parameters = append(parameters, "%"+mailSearch.Message+"%")
		parameters = append(parameters, "%"+mailSearch.Message+"%")
	}

	if len(strings.TrimSpace(mailSearch.From)) > 0 {
		sqlQuery += `
			AND mailitem.fromAddress LIKE ?
		`

		parameters = append(parameters, "%"+mailSearch.From+"%")
	}

	if len(strings.TrimSpace(mailSearch.To)) > 0 {
		sqlQuery += `
			AND mailitem.toAddressList LIKE ?
		`

		parameters = append(parameters, "%"+mailSearch.To+"%")
	}

	if len(strings.TrimSpace(mailSearch.Start)) > 0 {
		if date, err = time.Parse("2006-01-02", mailSearch.Start); err == nil {
			sqlQuery += `
				AND mailitem.dateSent >= ?
			`

			parameters = append(parameters, date)
		}
	}

	if len(strings.TrimSpace(mailSearch.End)) > 0 {
		if date, err = time.Parse("2006-01-02", mailSearch.End); err == nil {
			datePlusOne := date.Add(time.Hour * 24)

			sqlQuery += `
				AND mailitem.dateSent < ?
			`

			parameters = append(parameters, datePlusOne)
		}
	}

	return sqlQuery, parameters
}

func storeAttachments(mailItemID string, transaction *sql.Tx, attachments []*attachment.Attachment) error {
	var err error
	var attachmentID string

	for _, currentAttachment := range attachments {
		if attachmentID, err = mailitem.GenerateId(); err != nil {
			return fmt.Errorf("Error generating ID for attachment: %s", err.Error())
		}

		statement, err := transaction.Prepare(getInsertAttachmentQuery())
		if err != nil {
			return fmt.Errorf("Error preparing insert attachment statement: %s", err.Error())
		}

		_, err = statement.Exec(
			attachmentID,
			mailItemID,
			currentAttachment.Headers.FileName,
			currentAttachment.Headers.ContentType,
			currentAttachment.Contents,
		)

		if err != nil {
			return fmt.Errorf("Error executing insert attachment in StoreMail: %s", err.Error())
		}

		statement.Close()
		currentAttachment.Id = attachmentID
	}

	return nil
}
<|endoftext|>


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

/*
Package inotify implements a wrapper for the Linux inotify system.

Example:
    watcher, err := inotify.NewWatcher()
    if err != nil {
        log.Fatal(err)
    }
    err = watcher.Watch("/tmp")
    if err != nil {
        log.Fatal(err)
    }
    for {
        select {
        case ev := <-watcher.Event:
            log.Println("event:", ev)
        case err := <-watcher.Error:
            log.Println("error:", err)
        }
    }

*/
package inotify // import "golang.org/x/exp/inotify"

import (
	"errors"
	"fmt"
	"os"
	"strings"
	"sync"
	"syscall"
	"unsafe"
)

type Event struct {
	Mask   uint32 // Mask of events
	Cookie uint32 // Unique cookie associating related events (for rename(2))
	Name   string // File name (optional)
}

type watch struct {
	wd    uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
	flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
}

type Watcher struct {
	mu       sync.Mutex
	fd       int               // File descriptor (as returned by the inotify_init() syscall)
	watches  map[string]*watch // Map of inotify watches (key: path)
	paths    map[int]string    // Map of watched paths (key: watch descriptor)
	Error    chan error        // Errors are sent on this channel
	Event    chan *Event       // Events are returned on this channel
	done     chan bool         // Channel for sending a "quit message" to the reader goroutine
	isClosed bool              // Set to true when Close() is first called
}

// NewWatcher creates and returns a new inotify instance using inotify_init(2)
func NewWatcher() (*Watcher, error) {
	fd, errno := syscall.InotifyInit()
	if fd == -1 {
		return nil, os.NewSyscallError("inotify_init", errno)
	}
	w := &Watcher{
		fd:      fd,
		watches: make(map[string]*watch),
		paths:   make(map[int]string),
		Event:   make(chan *Event),
		Error:   make(chan error),
		done:    make(chan bool, 1),
	}

	go w.readEvents()
	return w, nil
}

// Close closes an inotify watcher instance
// It sends a message to the reader goroutine to quit and removes all watches
// associated with the inotify instance
func (w *Watcher) Close() error {
	if w.isClosed {
		return nil
	}
	w.isClosed = true

	// Send "quit" message to the reader goroutine
	w.done <- true
	for path := range w.watches {
		w.RemoveWatch(path)
	}

	return nil
}

// AddWatch adds path to the watched file set.
// The flags are interpreted as described in inotify_add_watch(2).
func (w *Watcher) AddWatch(path string, flags uint32) error {
	if w.isClosed {
		return errors.New("inotify instance already closed")
	}

	watchEntry, found := w.watches[path]
	if found {
		watchEntry.flags |= flags
		flags |= syscall.IN_MASK_ADD
	}

	w.mu.Lock() // synchronize with readEvents goroutine

	wd, err := syscall.InotifyAddWatch(w.fd, path, flags)
	if err != nil {
		w.mu.Unlock()
		return &os.PathError{
			Op:   "inotify_add_watch",
			Path: path,
			Err:  err,
		}
	}

	if !found {
		w.watches[path] = &watch{wd: uint32(wd), flags: flags}
		w.paths[wd] = path
	}
	w.mu.Unlock()
	return nil
}

// Watch adds path to the watched file set, watching all events.
func (w *Watcher) Watch(path string) error {
	return w.AddWatch(path, IN_ALL_EVENTS)
}

// RemoveWatch removes path from the watched file set.
func (w *Watcher) RemoveWatch(path string) error {
	watch, ok := w.watches[path]
	if !ok {
		return errors.New(fmt.Sprintf("can't remove non-existent inotify watch for: %s", path))
	}
	success, errno := syscall.InotifyRmWatch(w.fd, watch.wd)
	if success == -1 {
		return os.NewSyscallError("inotify_rm_watch", errno)
	}
	delete(w.watches, path)
	return nil
}

// readEvents reads from the inotify file descriptor, converts the
// received events into Event objects and sends them via the Event channel
func (w *Watcher) readEvents() {
	var buf [syscall.SizeofInotifyEvent * 4096]byte

	for {
		n, err := syscall.Read(w.fd, buf[:])
		// See if there is a message on the "done" channel
		var done bool
		select {
		case done = <-w.done:
		default:
		}

		// If EOF or a "done" message is received
		if n == 0 || done {
			// The syscall.Close can be slow.  Close
			// w.Event first.
			close(w.Event)
			err := syscall.Close(w.fd)
			if err != nil {
				w.Error <- os.NewSyscallError("close", err)
			}
			close(w.Error)
			return
		}
		if n < 0 {
			w.Error <- os.NewSyscallError("read", err)
			continue
		}
		if n < syscall.SizeofInotifyEvent {
			w.Error <- errors.New("inotify: short read in readEvents()")
			continue
		}

		var offset uint32 = 0
		// We don't know how many events we just read into the buffer
		// While the offset points to at least one whole event...
		for offset <= uint32(n-syscall.SizeofInotifyEvent) {
			// Point "raw" to the event in the buffer
			raw := (*syscall.InotifyEvent)(unsafe.Pointer(&buf[offset]))
			event := new(Event)
			event.Mask = uint32(raw.Mask)
			event.Cookie = uint32(raw.Cookie)
			nameLen := uint32(raw.Len)
			// If the event happened to the watched directory or the watched file, the kernel
			// doesn't append the filename to the event, but we would like to always fill the
			// the "Name" field with a valid filename. We retrieve the path of the watch from
			// the "paths" map.
			w.mu.Lock()
			event.Name = w.paths[int(raw.Wd)]
			w.mu.Unlock()
			if nameLen > 0 {
				// Point "bytes" at the first byte of the filename
				bytes := (*[syscall.PathMax]byte)(unsafe.Pointer(&buf[offset+syscall.SizeofInotifyEvent]))
				// The filename is padded with NUL bytes. TrimRight() gets rid of those.
				event.Name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000")
			}
			// Send the event on the events channel
			w.Event <- event

			// Move to the next event in the buffer
			offset += syscall.SizeofInotifyEvent + nameLen
		}
	}
}

// String formats the event e in the form
// "filename: 0xEventMask = IN_ACCESS|IN_ATTRIB_|..."
func (e *Event) String() string {
	var events string = ""

	m := e.Mask
	for _, b := range eventBits {
		if m&b.Value != 0 {
			m &^= b.Value
			events += "|" + b.Name
		}
	}

	if m != 0 {
		events += fmt.Sprintf("|%#x", m)
	}
	if len(events) > 0 {
		events = " == " + events[1:]
	}

	return fmt.Sprintf("%q: %#x%s", e.Name, e.Mask, events)
}

const (
	// Options for inotify_init() are not exported
	// IN_CLOEXEC    uint32 = syscall.IN_CLOEXEC
	// IN_NONBLOCK   uint32 = syscall.IN_NONBLOCK

	// Options for AddWatch
	IN_DONT_FOLLOW uint32 = syscall.IN_DONT_FOLLOW
	IN_ONESHOT     uint32 = syscall.IN_ONESHOT
	IN_ONLYDIR     uint32 = syscall.IN_ONLYDIR

	// The "IN_MASK_ADD" option is not exported, as AddWatch
	// adds it automatically, if there is already a watch for the given path
	// IN_MASK_ADD      uint32 = syscall.IN_MASK_ADD

	// Events
	IN_ACCESS        uint32 = syscall.IN_ACCESS
	IN_ALL_EVENTS    uint32 = syscall.IN_ALL_EVENTS
	IN_ATTRIB        uint32 = syscall.IN_ATTRIB
	IN_CLOSE         uint32 = syscall.IN_CLOSE
	IN_CLOSE_NOWRITE uint32 = syscall.IN_CLOSE_NOWRITE
	IN_CLOSE_WRITE   uint32 = syscall.IN_CLOSE_WRITE
	IN_CREATE        uint32 = syscall.IN_CREATE
	IN_DELETE        uint32 = syscall.IN_DELETE
	IN_DELETE_SELF   uint32 = syscall.IN_DELETE_SELF
	IN_MODIFY        uint32 = syscall.IN_MODIFY
	IN_MOVE          uint32 = syscall.IN_MOVE
	IN_MOVED_FROM    uint32 = syscall.IN_MOVED_FROM
	IN_MOVED_TO      uint32 = syscall.IN_MOVED_TO
	IN_MOVE_SELF     uint32 = syscall.IN_MOVE_SELF
	IN_OPEN          uint32 = syscall.IN_OPEN

	// Special events
	IN_ISDIR      uint32 = syscall.IN_ISDIR
	IN_IGNORED    uint32 = syscall.IN_IGNORED
	IN_Q_OVERFLOW uint32 = syscall.IN_Q_OVERFLOW
	IN_UNMOUNT    uint32 = syscall.IN_UNMOUNT
)

var eventBits = []struct {
	Value uint32
	Name  string
}{
	{IN_ACCESS, "IN_ACCESS"},
	{IN_ATTRIB, "IN_ATTRIB"},
	{IN_CLOSE, "IN_CLOSE"},
	{IN_CLOSE_NOWRITE, "IN_CLOSE_NOWRITE"},
	{IN_CLOSE_WRITE, "IN_CLOSE_WRITE"},
	{IN_CREATE, "IN_CREATE"},
	{IN_DELETE, "IN_DELETE"},
	{IN_DELETE_SELF, "IN_DELETE_SELF"},
	{IN_MODIFY, "IN_MODIFY"},
	{IN_MOVE, "IN_MOVE"},
	{IN_MOVED_FROM, "IN_MOVED_FROM"},
	{IN_MOVED_TO, "IN_MOVED_TO"},
	{IN_MOVE_SELF, "IN_MOVE_SELF"},
	{IN_OPEN, "IN_OPEN"},
	{IN_ISDIR, "IN_ISDIR"},
	{IN_IGNORED, "IN_IGNORED"},
	{IN_Q_OVERFLOW, "IN_Q_OVERFLOW"},
	{IN_UNMOUNT, "IN_UNMOUNT"},
}
<commit_msg>inotify: fix event printing<commit_after>// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

/*
Package inotify implements a wrapper for the Linux inotify system.

Example:
    watcher, err := inotify.NewWatcher()
    if err != nil {
        log.Fatal(err)
    }
    err = watcher.Watch("/tmp")
    if err != nil {
        log.Fatal(err)
    }
    for {
        select {
        case ev := <-watcher.Event:
            log.Println("event:", ev)
        case err := <-watcher.Error:
            log.Println("error:", err)
        }
    }

*/
package inotify // import "golang.org/x/exp/inotify"

import (
	"errors"
	"fmt"
	"os"
	"strings"
	"sync"
	"syscall"
	"unsafe"
)

type Event struct {
	Mask   uint32 // Mask of events
	Cookie uint32 // Unique cookie associating related events (for rename(2))
	Name   string // File name (optional)
}

type watch struct {
	wd    uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
	flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
}

type Watcher struct {
	mu       sync.Mutex
	fd       int               // File descriptor (as returned by the inotify_init() syscall)
	watches  map[string]*watch // Map of inotify watches (key: path)
	paths    map[int]string    // Map of watched paths (key: watch descriptor)
	Error    chan error        // Errors are sent on this channel
	Event    chan *Event       // Events are returned on this channel
	done     chan bool         // Channel for sending a "quit message" to the reader goroutine
	isClosed bool              // Set to true when Close() is first called
}

// NewWatcher creates and returns a new inotify instance using inotify_init(2)
func NewWatcher() (*Watcher, error) {
	fd, errno := syscall.InotifyInit()
	if fd == -1 {
		return nil, os.NewSyscallError("inotify_init", errno)
	}
	w := &Watcher{
		fd:      fd,
		watches: make(map[string]*watch),
		paths:   make(map[int]string),
		Event:   make(chan *Event),
		Error:   make(chan error),
		done:    make(chan bool, 1),
	}

	go w.readEvents()
	return w, nil
}

// Close closes an inotify watcher instance
// It sends a message to the reader goroutine to quit and removes all watches
// associated with the inotify instance
func (w *Watcher) Close() error {
	if w.isClosed {
		return nil
	}
	w.isClosed = true

	// Send "quit" message to the reader goroutine
	w.done <- true
	for path := range w.watches {
		w.RemoveWatch(path)
	}

	return nil
}

// AddWatch adds path to the watched file set.
// The flags are interpreted as described in inotify_add_watch(2).
func (w *Watcher) AddWatch(path string, flags uint32) error {
	if w.isClosed {
		return errors.New("inotify instance already closed")
	}

	watchEntry, found := w.watches[path]
	if found {
		watchEntry.flags |= flags
		flags |= syscall.IN_MASK_ADD
	}

	w.mu.Lock() // synchronize with readEvents goroutine

	wd, err := syscall.InotifyAddWatch(w.fd, path, flags)
	if err != nil {
		w.mu.Unlock()
		return &os.PathError{
			Op:   "inotify_add_watch",
			Path: path,
			Err:  err,
		}
	}

	if !found {
		w.watches[path] = &watch{wd: uint32(wd), flags: flags}
		w.paths[wd] = path
	}
	w.mu.Unlock()
	return nil
}

// Watch adds path to the watched file set, watching all events.
func (w *Watcher) Watch(path string) error {
	return w.AddWatch(path, IN_ALL_EVENTS)
}

// RemoveWatch removes path from the watched file set.
func (w *Watcher) RemoveWatch(path string) error {
	watch, ok := w.watches[path]
	if !ok {
		return errors.New(fmt.Sprintf("can't remove non-existent inotify watch for: %s", path))
	}
	success, errno := syscall.InotifyRmWatch(w.fd, watch.wd)
	if success == -1 {
		return os.NewSyscallError("inotify_rm_watch", errno)
	}
	delete(w.watches, path)
	return nil
}

// readEvents reads from the inotify file descriptor, converts the
// received events into Event objects and sends them via the Event channel
func (w *Watcher) readEvents() {
	var buf [syscall.SizeofInotifyEvent * 4096]byte

	for {
		n, err := syscall.Read(w.fd, buf[:])
		// See if there is a message on the "done" channel
		var done bool
		select {
		case done = <-w.done:
		default:
		}

		// If EOF or a "done" message is received
		if n == 0 || done {
			// The syscall.Close can be slow.  Close
			// w.Event first.
			close(w.Event)
			err := syscall.Close(w.fd)
			if err != nil {
				w.Error <- os.NewSyscallError("close", err)
			}
			close(w.Error)
			return
		}
		if n < 0 {
			w.Error <- os.NewSyscallError("read", err)
			continue
		}
		if n < syscall.SizeofInotifyEvent {
			w.Error <- errors.New("inotify: short read in readEvents()")
			continue
		}

		var offset uint32 = 0
		// We don't know how many events we just read into the buffer
		// While the offset points to at least one whole event...
		for offset <= uint32(n-syscall.SizeofInotifyEvent) {
			// Point "raw" to the event in the buffer
			raw := (*syscall.InotifyEvent)(unsafe.Pointer(&buf[offset]))
			event := new(Event)
			event.Mask = uint32(raw.Mask)
			event.Cookie = uint32(raw.Cookie)
			nameLen := uint32(raw.Len)
			// If the event happened to the watched directory or the watched file, the kernel
			// doesn't append the filename to the event, but we would like to always fill the
			// the "Name" field with a valid filename. We retrieve the path of the watch from
			// the "paths" map.
			w.mu.Lock()
			event.Name = w.paths[int(raw.Wd)]
			w.mu.Unlock()
			if nameLen > 0 {
				// Point "bytes" at the first byte of the filename
				bytes := (*[syscall.PathMax]byte)(unsafe.Pointer(&buf[offset+syscall.SizeofInotifyEvent]))
				// The filename is padded with NUL bytes. TrimRight() gets rid of those.
				event.Name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000")
			}
			// Send the event on the events channel
			w.Event <- event

			// Move to the next event in the buffer
			offset += syscall.SizeofInotifyEvent + nameLen
		}
	}
}

// String formats the event e in the form
// "filename: 0xEventMask = IN_ACCESS|IN_ATTRIB_|..."
func (e *Event) String() string {
	var events string = ""

	m := e.Mask
	for _, b := range eventBits {
		if m&b.Value == b.Value {
			m &^= b.Value
			events += "|" + b.Name
		}
	}

	if m != 0 {
		events += fmt.Sprintf("|%#x", m)
	}
	if len(events) > 0 {
		events = " == " + events[1:]
	}

	return fmt.Sprintf("%q: %#x%s", e.Name, e.Mask, events)
}

const (
	// Options for inotify_init() are not exported
	// IN_CLOEXEC    uint32 = syscall.IN_CLOEXEC
	// IN_NONBLOCK   uint32 = syscall.IN_NONBLOCK

	// Options for AddWatch
	IN_DONT_FOLLOW uint32 = syscall.IN_DONT_FOLLOW
	IN_ONESHOT     uint32 = syscall.IN_ONESHOT
	IN_ONLYDIR     uint32 = syscall.IN_ONLYDIR

	// The "IN_MASK_ADD" option is not exported, as AddWatch
	// adds it automatically, if there is already a watch for the given path
	// IN_MASK_ADD      uint32 = syscall.IN_MASK_ADD

	// Events
	IN_ACCESS        uint32 = syscall.IN_ACCESS
	IN_ALL_EVENTS    uint32 = syscall.IN_ALL_EVENTS
	IN_ATTRIB        uint32 = syscall.IN_ATTRIB
	IN_CLOSE         uint32 = syscall.IN_CLOSE
	IN_CLOSE_NOWRITE uint32 = syscall.IN_CLOSE_NOWRITE
	IN_CLOSE_WRITE   uint32 = syscall.IN_CLOSE_WRITE
	IN_CREATE        uint32 = syscall.IN_CREATE
	IN_DELETE        uint32 = syscall.IN_DELETE
	IN_DELETE_SELF   uint32 = syscall.IN_DELETE_SELF
	IN_MODIFY        uint32 = syscall.IN_MODIFY
	IN_MOVE          uint32 = syscall.IN_MOVE
	IN_MOVED_FROM    uint32 = syscall.IN_MOVED_FROM
	IN_MOVED_TO      uint32 = syscall.IN_MOVED_TO
	IN_MOVE_SELF     uint32 = syscall.IN_MOVE_SELF
	IN_OPEN          uint32 = syscall.IN_OPEN

	// Special events
	IN_ISDIR      uint32 = syscall.IN_ISDIR
	IN_IGNORED    uint32 = syscall.IN_IGNORED
	IN_Q_OVERFLOW uint32 = syscall.IN_Q_OVERFLOW
	IN_UNMOUNT    uint32 = syscall.IN_UNMOUNT
)

var eventBits = []struct {
	Value uint32
	Name  string
}{
	{IN_ACCESS, "IN_ACCESS"},
	{IN_ATTRIB, "IN_ATTRIB"},
	{IN_CLOSE, "IN_CLOSE"},
	{IN_CLOSE_NOWRITE, "IN_CLOSE_NOWRITE"},
	{IN_CLOSE_WRITE, "IN_CLOSE_WRITE"},
	{IN_CREATE, "IN_CREATE"},
	{IN_DELETE, "IN_DELETE"},
	{IN_DELETE_SELF, "IN_DELETE_SELF"},
	{IN_MODIFY, "IN_MODIFY"},
	{IN_MOVE, "IN_MOVE"},
	{IN_MOVED_FROM, "IN_MOVED_FROM"},
	{IN_MOVED_TO, "IN_MOVED_TO"},
	{IN_MOVE_SELF, "IN_MOVE_SELF"},
	{IN_OPEN, "IN_OPEN"},
	{IN_ISDIR, "IN_ISDIR"},
	{IN_IGNORED, "IN_IGNORED"},
	{IN_Q_OVERFLOW, "IN_Q_OVERFLOW"},
	{IN_UNMOUNT, "IN_UNMOUNT"},
}
<|endoftext|>


<commit_before>package config

import (
	"errors"
	"strconv"
	"strings"
	"time"
)

// CurrentVersionNumber is the current application's version literal
const CurrentVersionNumber = "0.3.4"

// Version regulates checking if the most recent version is run
type Version struct {
	// Current is the ipfs version for which config was generated
	Current string

	// Check signals how to react on updates:
	// - "ignore" for not checking
	// - "warn" for issuing a warning and proceeding
	// - "error" for exiting with an error
	Check string

	// CheckDate is a timestamp for the last time API endpoint was checked for updates
	CheckDate time.Time

	// CheckPeriod is the time duration over which the update check will not be performed
	// (Note: cannot use time.Duration because marshalling with json breaks it)
	CheckPeriod string

	// AutoUpdate is optional
	AutoUpdate AutoUpdateSetting
}

// supported Version.Check values
const (
	// CheckError value for Version.Check to raise error and exit if version is obsolete
	CheckError = "error"

	// CheckWarn value for Version.Check to show warning message if version is obsolete
	CheckWarn = "warn"

	// CheckIgnore value for Version.Check to not perform update check
	CheckIgnore = "ignore"
)

// AutoUpdateSetting implements json.Unmarshaler to check values in config
type AutoUpdateSetting int

// AutoUpdateSetting values
const (
	AutoUpdateNever AutoUpdateSetting = iota // do not auto-update
	AutoUpdatePatch                          // only on new patch versions
	AutoUpdateMinor                          // on new minor or patch versions (Default)
	AutoUpdateMajor                          // on all, even Major, version changes
)

// ErrUnknownAutoUpdateSetting is returned when an unknown value is read from the config
var ErrUnknownAutoUpdateSetting = errors.New("unknown value for AutoUpdate")

// defaultCheckPeriod governs h
var defaultCheckPeriod = time.Hour * 48

// UnmarshalJSON checks the input against known strings
func (s *AutoUpdateSetting) UnmarshalJSON(in []byte) error {

	switch strings.ToLower(string(in)) {
	case `"never"`:
		*s = AutoUpdateNever
	case `"major"`:
		*s = AutoUpdateMajor
	case `"minor"`:
		*s = AutoUpdateMinor
	case `"patch"`:
		*s = AutoUpdatePatch
	default:
		*s = AutoUpdateMinor
		return ErrUnknownAutoUpdateSetting
	}
	return nil
}

// MarshalJSON converts the value back to JSON string
func (s AutoUpdateSetting) MarshalJSON() ([]byte, error) {
	return []byte(`"` + s.String() + `"`), nil
}

// String converts valye to human readable string
func (s AutoUpdateSetting) String() string {
	switch s {
	case AutoUpdateNever:
		return "never"
	case AutoUpdateMajor:
		return "major"
	case AutoUpdateMinor:
		return "minor"
	case AutoUpdatePatch:
		return "patch"
	default:
		return ErrUnknownAutoUpdateSetting.Error()
	}
}

func (v *Version) checkPeriodDuration() time.Duration {
	d, err := strconv.Atoi(v.CheckPeriod)
	if err != nil {
		log.Warning("config.Version.CheckPeriod parse error. Using default.")
		return defaultCheckPeriod
	}
	return time.Duration(d)
}

// ShouldCheckForUpdate returns if update check API endpoint is needed for this specific runtime
func (v *Version) ShouldCheckForUpdate() bool {

	period := v.checkPeriodDuration()
	if v.Check == CheckIgnore || v.CheckDate.Add(period).After(time.Now()) {
		return false
	}
	return true
}

// VersionDefaultValue returns the default version config value (for init).
func VersionDefaultValue() Version {
	return Version{
		Current:     CurrentVersionNumber,
		Check:       "error",
		CheckPeriod: strconv.Itoa(int(defaultCheckPeriod)),
		AutoUpdate:  AutoUpdateMinor,
	}
}
<commit_msg>version bump to 0.3.5<commit_after>package config

import (
	"errors"
	"strconv"
	"strings"
	"time"
)

// CurrentVersionNumber is the current application's version literal
const CurrentVersionNumber = "0.3.5"

// Version regulates checking if the most recent version is run
type Version struct {
	// Current is the ipfs version for which config was generated
	Current string

	// Check signals how to react on updates:
	// - "ignore" for not checking
	// - "warn" for issuing a warning and proceeding
	// - "error" for exiting with an error
	Check string

	// CheckDate is a timestamp for the last time API endpoint was checked for updates
	CheckDate time.Time

	// CheckPeriod is the time duration over which the update check will not be performed
	// (Note: cannot use time.Duration because marshalling with json breaks it)
	CheckPeriod string

	// AutoUpdate is optional
	AutoUpdate AutoUpdateSetting
}

// supported Version.Check values
const (
	// CheckError value for Version.Check to raise error and exit if version is obsolete
	CheckError = "error"

	// CheckWarn value for Version.Check to show warning message if version is obsolete
	CheckWarn = "warn"

	// CheckIgnore value for Version.Check to not perform update check
	CheckIgnore = "ignore"
)

// AutoUpdateSetting implements json.Unmarshaler to check values in config
type AutoUpdateSetting int

// AutoUpdateSetting values
const (
	AutoUpdateNever AutoUpdateSetting = iota // do not auto-update
	AutoUpdatePatch                          // only on new patch versions
	AutoUpdateMinor                          // on new minor or patch versions (Default)
	AutoUpdateMajor                          // on all, even Major, version changes
)

// ErrUnknownAutoUpdateSetting is returned when an unknown value is read from the config
var ErrUnknownAutoUpdateSetting = errors.New("unknown value for AutoUpdate")

// defaultCheckPeriod governs h
var defaultCheckPeriod = time.Hour * 48

// UnmarshalJSON checks the input against known strings
func (s *AutoUpdateSetting) UnmarshalJSON(in []byte) error {

	switch strings.ToLower(string(in)) {
	case `"never"`:
		*s = AutoUpdateNever
	case `"major"`:
		*s = AutoUpdateMajor
	case `"minor"`:
		*s = AutoUpdateMinor
	case `"patch"`:
		*s = AutoUpdatePatch
	default:
		*s = AutoUpdateMinor
		return ErrUnknownAutoUpdateSetting
	}
	return nil
}

// MarshalJSON converts the value back to JSON string
func (s AutoUpdateSetting) MarshalJSON() ([]byte, error) {
	return []byte(`"` + s.String() + `"`), nil
}

// String converts valye to human readable string
func (s AutoUpdateSetting) String() string {
	switch s {
	case AutoUpdateNever:
		return "never"
	case AutoUpdateMajor:
		return "major"
	case AutoUpdateMinor:
		return "minor"
	case AutoUpdatePatch:
		return "patch"
	default:
		return ErrUnknownAutoUpdateSetting.Error()
	}
}

func (v *Version) checkPeriodDuration() time.Duration {
	d, err := strconv.Atoi(v.CheckPeriod)
	if err != nil {
		log.Warning("config.Version.CheckPeriod parse error. Using default.")
		return defaultCheckPeriod
	}
	return time.Duration(d)
}

// ShouldCheckForUpdate returns if update check API endpoint is needed for this specific runtime
func (v *Version) ShouldCheckForUpdate() bool {

	period := v.checkPeriodDuration()
	if v.Check == CheckIgnore || v.CheckDate.Add(period).After(time.Now()) {
		return false
	}
	return true
}

// VersionDefaultValue returns the default version config value (for init).
func VersionDefaultValue() Version {
	return Version{
		Current:     CurrentVersionNumber,
		Check:       "error",
		CheckPeriod: strconv.Itoa(int(defaultCheckPeriod)),
		AutoUpdate:  AutoUpdateMinor,
	}
}
<|endoftext|>


<commit_before>package execext

import (
	"context"
	"errors"
	"io"
	"os"
	"path/filepath"
	"strings"

	"mvdan.cc/sh/v3/expand"
	"mvdan.cc/sh/v3/interp"
	"mvdan.cc/sh/v3/shell"
	"mvdan.cc/sh/v3/syntax"
)

// RunCommandOptions is the options for the RunCommand func
type RunCommandOptions struct {
	Command string
	Dir     string
	Env     []string
	Stdin   io.Reader
	Stdout  io.Writer
	Stderr  io.Writer
}

var (
	// ErrNilOptions is returned when a nil options is given
	ErrNilOptions = errors.New("execext: nil options given")
)

// RunCommand runs a shell command
func RunCommand(ctx context.Context, opts *RunCommandOptions) error {
	if opts == nil {
		return ErrNilOptions
	}

	p, err := syntax.NewParser().Parse(strings.NewReader(opts.Command), "")
	if err != nil {
		return err
	}

	environ := opts.Env
	if len(environ) == 0 {
		environ = os.Environ()
	}

	r, err := interp.New(
		interp.Params("-e"),
		interp.Env(expand.ListEnviron(environ...)),
		interp.OpenHandler(openHandler),
		interp.StdIO(opts.Stdin, opts.Stdout, opts.Stderr),
		dirOption(opts.Dir),
	)
	if err != nil {
		return err
	}
	return r.Run(ctx, p)
}

// IsExitError returns true the given error is an exis status error
func IsExitError(err error) bool {
	if _, ok := interp.IsExitStatus(err); ok {
		return true
	}
	return false
}

// Expand is a helper to mvdan.cc/shell.Fields that returns the first field
// if available.
func Expand(s string) (string, error) {
	s = filepath.ToSlash(s)
	s = strings.Replace(s, " ", `\ `, -1)
	fields, err := shell.Fields(s, nil)
	if err != nil {
		return "", err
	}
	if len(fields) > 0 {
		return fields[0], nil
	}
	return "", nil
}

func openHandler(ctx context.Context, path string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) {
	if path == "/dev/null" {
		return devNull{}, nil
	}
	return interp.DefaultOpenHandler()(ctx, path, flag, perm)
}

func dirOption(path string) interp.RunnerOption {
	return func(r *interp.Runner) error {
		err := interp.Dir(path)(r)
		if err == nil {
			return nil
		}

		// If the specified directory doesn't exist, it will be created later.
		// Therefore, even if `interp.Dir` method returns an error, the
		// directory path should be set only when the directory cannot be found.
		if absPath, _ := filepath.Abs(path); absPath != "" {
			if _, err := os.Stat(absPath); os.IsNotExist(err) {
				r.Dir = absPath
				return nil
			}
		}

		return err
	}
}
<commit_msg>execext.RunCommand: fix: do not pass a cancellable context to mvdan.cc/sh<commit_after>package execext

import (
	"context"
	"errors"
	"io"
	"os"
	"path/filepath"
	"strings"

	"mvdan.cc/sh/v3/expand"
	"mvdan.cc/sh/v3/interp"
	"mvdan.cc/sh/v3/shell"
	"mvdan.cc/sh/v3/syntax"
)

// RunCommandOptions is the options for the RunCommand func
type RunCommandOptions struct {
	Command string
	Dir     string
	Env     []string
	Stdin   io.Reader
	Stdout  io.Writer
	Stderr  io.Writer
}

var (
	// ErrNilOptions is returned when a nil options is given
	ErrNilOptions = errors.New("execext: nil options given")
)

// RunCommand runs a shell command
func RunCommand(ctx context.Context, opts *RunCommandOptions) error {
	if opts == nil {
		return ErrNilOptions
	}

	p, err := syntax.NewParser().Parse(strings.NewReader(opts.Command), "")
	if err != nil {
		return err
	}

	environ := opts.Env
	if len(environ) == 0 {
		environ = os.Environ()
	}

	r, err := interp.New(
		interp.Params("-e"),
		interp.Env(expand.ListEnviron(environ...)),
		interp.OpenHandler(openHandler),
		interp.StdIO(opts.Stdin, opts.Stdout, opts.Stderr),
		dirOption(opts.Dir),
	)
	if err != nil {
		return err
	}

	// We used to pass to interp.Runner a context that was cancelled on reception of a
	// OS signal. This caused the Runner to terminate the subprocess abruptly.
	// The correct behavior instead is for us to completely ignore the signal and let
	// the subprocess deal with it. If the subprocess doesn't handle the signal, it will
	// be terminated. If the subprocess does handle the signal, it knows better than us
	// wether it wants to cleanup and terminate or do something different.
	// See https://github.com/go-task/task/issues/458 for details.
	// So now we pass an empty context just to make the API of interp.Runner happy
	return r.Run(context.Background(), p)
}

// IsExitError returns true the given error is an exis status error
func IsExitError(err error) bool {
	if _, ok := interp.IsExitStatus(err); ok {
		return true
	}
	return false
}

// Expand is a helper to mvdan.cc/shell.Fields that returns the first field
// if available.
func Expand(s string) (string, error) {
	s = filepath.ToSlash(s)
	s = strings.Replace(s, " ", `\ `, -1)
	fields, err := shell.Fields(s, nil)
	if err != nil {
		return "", err
	}
	if len(fields) > 0 {
		return fields[0], nil
	}
	return "", nil
}

func openHandler(ctx context.Context, path string, flag int, perm os.FileMode) (io.ReadWriteCloser, error) {
	if path == "/dev/null" {
		return devNull{}, nil
	}
	return interp.DefaultOpenHandler()(ctx, path, flag, perm)
}

func dirOption(path string) interp.RunnerOption {
	return func(r *interp.Runner) error {
		err := interp.Dir(path)(r)
		if err == nil {
			return nil
		}

		// If the specified directory doesn't exist, it will be created later.
		// Therefore, even if `interp.Dir` method returns an error, the
		// directory path should be set only when the directory cannot be found.
		if absPath, _ := filepath.Abs(path); absPath != "" {
			if _, err := os.Stat(absPath); os.IsNotExist(err) {
				r.Dir = absPath
				return nil
			}
		}

		return err
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2020 The Ceph-CSI 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 rbd

import (
	"context"
	"errors"
	"fmt"

	"github.com/ceph/ceph-csi/internal/util"
	"github.com/ceph/ceph-csi/internal/util/log"
)

func createRBDClone(
	ctx context.Context,
	parentVol, cloneRbdVol *rbdVolume,
	snap *rbdSnapshot,
	cr *util.Credentials) error {
	// create snapshot
	err := parentVol.createSnapshot(ctx, snap)
	if err != nil {
		log.ErrorLog(ctx, "failed to create snapshot %s: %v", snap, err)

		return err
	}

	snap.RbdImageName = parentVol.RbdImageName
	// create clone image and delete snapshot
	err = cloneRbdVol.cloneRbdImageFromSnapshot(ctx, snap, parentVol)
	if err != nil {
		log.ErrorLog(
			ctx,
			"failed to clone rbd image %s from snapshot %s: %v",
			cloneRbdVol.RbdImageName,
			snap.RbdSnapName,
			err)
		err = fmt.Errorf(
			"failed to clone rbd image %s from snapshot %s: %w",
			cloneRbdVol.RbdImageName,
			snap.RbdSnapName,
			err)
	}
	errSnap := parentVol.deleteSnapshot(ctx, snap)
	if errSnap != nil {
		log.ErrorLog(ctx, "failed to delete snapshot: %v", errSnap)
		delErr := deleteImage(ctx, cloneRbdVol, cr)
		if delErr != nil {
			log.ErrorLog(ctx, "failed to delete rbd image: %s with error: %v", cloneRbdVol, delErr)
		}

		return err
	}

	err = cloneRbdVol.getImageInfo()
	if err != nil {
		log.ErrorLog(ctx, "failed to get rbd image: %s details with error: %v", cloneRbdVol, err)
		delErr := deleteImage(ctx, cloneRbdVol, cr)
		if delErr != nil {
			log.ErrorLog(ctx, "failed to delete rbd image: %s with error: %v", cloneRbdVol, delErr)
		}

		return err
	}

	return nil
}

// cleanUpSnapshot removes the RBD-snapshot (rbdSnap) from the RBD-image
// (parentVol) and deletes the RBD-image rbdVol.
func cleanUpSnapshot(
	ctx context.Context,
	parentVol *rbdVolume,
	rbdSnap *rbdSnapshot,
	rbdVol *rbdVolume,
	cr *util.Credentials) error {
	err := parentVol.deleteSnapshot(ctx, rbdSnap)
	if err != nil {
		if !errors.Is(err, ErrSnapNotFound) {
			log.ErrorLog(ctx, "failed to delete snapshot %q: %v", rbdSnap, err)

			return err
		}
	}

	if rbdVol != nil {
		err := deleteImage(ctx, rbdVol, cr)
		if err != nil {
			if !errors.Is(err, ErrImageNotFound) {
				log.ErrorLog(ctx, "failed to delete rbd image %q with error: %v", rbdVol, err)

				return err
			}
		}
	}

	return nil
}

func generateVolFromSnap(rbdSnap *rbdSnapshot) *rbdVolume {
	vol := new(rbdVolume)
	vol.ClusterID = rbdSnap.ClusterID
	vol.VolID = rbdSnap.VolID
	vol.Monitors = rbdSnap.Monitors
	vol.Pool = rbdSnap.Pool
	vol.JournalPool = rbdSnap.JournalPool
	vol.RadosNamespace = rbdSnap.RadosNamespace
	vol.RbdImageName = rbdSnap.RbdSnapName
	vol.ImageID = rbdSnap.ImageID
	// copyEncryptionConfig cannot be used here because the volume and the
	// snapshot will have the same volumeID which cases the panic in
	// copyEncryptionConfig function.
	vol.encryption = rbdSnap.encryption

	return vol
}

func undoSnapshotCloning(
	ctx context.Context,
	parentVol *rbdVolume,
	rbdSnap *rbdSnapshot,
	cloneVol *rbdVolume,
	cr *util.Credentials) error {
	err := cleanUpSnapshot(ctx, parentVol, rbdSnap, cloneVol, cr)
	if err != nil {
		log.ErrorLog(ctx, "failed to clean up  %s or %s: %v", cloneVol, rbdSnap, err)

		return err
	}
	err = undoSnapReservation(ctx, rbdSnap, cr)

	return err
}
<commit_msg>rbd: remove extra getImageInfo API call<commit_after>/*
Copyright 2020 The Ceph-CSI 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 rbd

import (
	"context"
	"errors"
	"fmt"

	"github.com/ceph/ceph-csi/internal/util"
	"github.com/ceph/ceph-csi/internal/util/log"
)

func createRBDClone(
	ctx context.Context,
	parentVol, cloneRbdVol *rbdVolume,
	snap *rbdSnapshot,
	cr *util.Credentials) error {
	// create snapshot
	err := parentVol.createSnapshot(ctx, snap)
	if err != nil {
		log.ErrorLog(ctx, "failed to create snapshot %s: %v", snap, err)

		return err
	}

	snap.RbdImageName = parentVol.RbdImageName
	// create clone image and delete snapshot
	err = cloneRbdVol.cloneRbdImageFromSnapshot(ctx, snap, parentVol)
	if err != nil {
		log.ErrorLog(
			ctx,
			"failed to clone rbd image %s from snapshot %s: %v",
			cloneRbdVol.RbdImageName,
			snap.RbdSnapName,
			err)
		err = fmt.Errorf(
			"failed to clone rbd image %s from snapshot %s: %w",
			cloneRbdVol.RbdImageName,
			snap.RbdSnapName,
			err)
	}
	errSnap := parentVol.deleteSnapshot(ctx, snap)
	if errSnap != nil {
		log.ErrorLog(ctx, "failed to delete snapshot: %v", errSnap)
		delErr := deleteImage(ctx, cloneRbdVol, cr)
		if delErr != nil {
			log.ErrorLog(ctx, "failed to delete rbd image: %s with error: %v", cloneRbdVol, delErr)
		}

		return err
	}

	return nil
}

// cleanUpSnapshot removes the RBD-snapshot (rbdSnap) from the RBD-image
// (parentVol) and deletes the RBD-image rbdVol.
func cleanUpSnapshot(
	ctx context.Context,
	parentVol *rbdVolume,
	rbdSnap *rbdSnapshot,
	rbdVol *rbdVolume,
	cr *util.Credentials) error {
	err := parentVol.deleteSnapshot(ctx, rbdSnap)
	if err != nil {
		if !errors.Is(err, ErrSnapNotFound) {
			log.ErrorLog(ctx, "failed to delete snapshot %q: %v", rbdSnap, err)

			return err
		}
	}

	if rbdVol != nil {
		err := deleteImage(ctx, rbdVol, cr)
		if err != nil {
			if !errors.Is(err, ErrImageNotFound) {
				log.ErrorLog(ctx, "failed to delete rbd image %q with error: %v", rbdVol, err)

				return err
			}
		}
	}

	return nil
}

func generateVolFromSnap(rbdSnap *rbdSnapshot) *rbdVolume {
	vol := new(rbdVolume)
	vol.ClusterID = rbdSnap.ClusterID
	vol.VolID = rbdSnap.VolID
	vol.Monitors = rbdSnap.Monitors
	vol.Pool = rbdSnap.Pool
	vol.JournalPool = rbdSnap.JournalPool
	vol.RadosNamespace = rbdSnap.RadosNamespace
	vol.RbdImageName = rbdSnap.RbdSnapName
	vol.ImageID = rbdSnap.ImageID
	// copyEncryptionConfig cannot be used here because the volume and the
	// snapshot will have the same volumeID which cases the panic in
	// copyEncryptionConfig function.
	vol.encryption = rbdSnap.encryption

	return vol
}

func undoSnapshotCloning(
	ctx context.Context,
	parentVol *rbdVolume,
	rbdSnap *rbdSnapshot,
	cloneVol *rbdVolume,
	cr *util.Credentials) error {
	err := cleanUpSnapshot(ctx, parentVol, rbdSnap, cloneVol, cr)
	if err != nil {
		log.ErrorLog(ctx, "failed to clean up  %s or %s: %v", cloneVol, rbdSnap, err)

		return err
	}
	err = undoSnapReservation(ctx, rbdSnap, cr)

	return err
}
<|endoftext|>


<commit_before>package service

import (
	"context"
	"errors"
	"fmt"
	"log"
	"strings"
	"time"

	"github.com/sanity-io/litter"
)

var (
	// ErrInvalidContent is used for invalid content.
	ErrInvalidContent = errors.New("invalid content")
	// ErrInvalidSpoiler used for invalid spoiler title.
	ErrInvalidSpoiler = errors.New("invalid spoiler")
)

// Post model.
type Post struct {
	ID        int64     `json:"id"`
	UserID    int64     `json:"-"`
	Content   string    `json:"content"`
	SpoilerOf *string   `json:"spoilerOf"`
	NSFW      bool      `json:"nsfw"`
	CreatedAt time.Time `json:"createdAt"`
	User      *User     `json:"user,omitempty"`
	Mine      bool      `json:"mine"`
}

// CreatePost publishes a post the the user timeline and fan-outs it to his followers.
func (s *Service) CreatePost(
	ctx context.Context,
	content string,
	spoilerOf *string,
	nsfw bool,
) (TimelineItem, error) {
	var ti TimelineItem
	uid, ok := ctx.Value(KeyAuthUserID).(int64)
	if !ok {
		return ti, ErrUnauthenticated
	}

	content = strings.TrimSpace(content)
	if content == "" || len([]rune(content)) > 480 {
		return ti, ErrInvalidContent
	}

	if spoilerOf != nil {
		*spoilerOf = strings.TrimSpace(*spoilerOf)
		if *spoilerOf == "" || len([]rune(*spoilerOf)) > 64 {
			return ti, ErrInvalidSpoiler
		}
	}

	tx, err := s.db.BeginTx(ctx, nil)
	if err != nil {
		return ti, fmt.Errorf("could not begin tx: %v", err)
	}

	defer tx.Rollback()

	query := "INSERT INTO posts (user_id, content, spoiler_of, nsfw) VALUES ($1, $2, $3, $4) " +
		"RETURNING id, created_at"
	if err = tx.QueryRowContext(ctx, query, uid, content, spoilerOf, nsfw).
		Scan(&ti.Post.ID, &ti.Post.CreatedAt); err != nil {
		return ti, fmt.Errorf("could not insert post: %v", err)
	}

	ti.Post.UserID = uid
	ti.Post.Content = content
	ti.Post.SpoilerOf = spoilerOf
	ti.Post.NSFW = nsfw
	ti.Post.Mine = true

	query = "INSERT INTO timeline (user_id, post_id) VALUES ($1, $2) RETURNING id"
	if err = tx.QueryRowContext(ctx, query, uid, ti.Post.ID).Scan(&ti.ID); err != nil {
		return ti, fmt.Errorf("could not insert timeline item: %v", err)
	}

	ti.UserID = uid
	ti.PostID = ti.Post.ID

	if err = tx.Commit(); err != nil {
		return ti, fmt.Errorf("could not commit to create post: %v", err)
	}

	go func(p Post) {
		u, err := s.userByID(context.Background(), p.UserID)
		if err != nil {
			log.Printf("could not get post user: %v\n", err)
			return
		}

		p.User = &u
		p.Mine = false

		tt, err := s.fanoutPost(p)
		if err != nil {
			log.Printf("could not fanout post: %v\n", err)
			return
		}

		for _, ti = range tt {
			log.Println(litter.Sdump(ti))
			// TODO: broadcast timeline items.
		}
	}(ti.Post)

	return ti, nil
}

func (s *Service) fanoutPost(p Post) ([]TimelineItem, error) {
	query := "INSERT INTO timeline (user_id, post_id) " +
		"SELECT follower_id, $1 FROM follows WHERE followee_id = $2 " +
		"RETURNING id, user_id"
	rows, err := s.db.Query(query, p.ID, p.UserID)
	if err != nil {
		return nil, fmt.Errorf("could not insert timeline: %v", err)
	}

	defer rows.Close()

	tt := []TimelineItem{}
	for rows.Next() {
		var ti TimelineItem
		if err = rows.Scan(&ti.ID, &ti.UserID); err != nil {
			return nil, fmt.Errorf("could not scan timeline item: %v", err)
		}

		ti.PostID = p.ID
		ti.Post = p
		tt = append(tt, ti)
	}

	if err = rows.Err(); err != nil {
		return nil, fmt.Errorf("could not iterate timeline rows: %v", err)
	}

	return tt, nil
}
<commit_msg>TODO: notify each mentioned user in posts<commit_after>package service

import (
	"context"
	"errors"
	"fmt"
	"log"
	"strings"
	"time"

	"github.com/sanity-io/litter"
)

var (
	// ErrInvalidContent is used for invalid content.
	ErrInvalidContent = errors.New("invalid content")
	// ErrInvalidSpoiler used for invalid spoiler title.
	ErrInvalidSpoiler = errors.New("invalid spoiler")
)

// Post model.
type Post struct {
	ID        int64     `json:"id"`
	UserID    int64     `json:"-"`
	Content   string    `json:"content"`
	SpoilerOf *string   `json:"spoilerOf"`
	NSFW      bool      `json:"nsfw"`
	CreatedAt time.Time `json:"createdAt"`
	User      *User     `json:"user,omitempty"`
	Mine      bool      `json:"mine"`
}

// CreatePost publishes a post the the user timeline and fan-outs it to his followers.
func (s *Service) CreatePost(
	ctx context.Context,
	content string,
	spoilerOf *string,
	nsfw bool,
) (TimelineItem, error) {
	var ti TimelineItem
	uid, ok := ctx.Value(KeyAuthUserID).(int64)
	if !ok {
		return ti, ErrUnauthenticated
	}

	content = strings.TrimSpace(content)
	if content == "" || len([]rune(content)) > 480 {
		return ti, ErrInvalidContent
	}

	if spoilerOf != nil {
		*spoilerOf = strings.TrimSpace(*spoilerOf)
		if *spoilerOf == "" || len([]rune(*spoilerOf)) > 64 {
			return ti, ErrInvalidSpoiler
		}
	}

	tx, err := s.db.BeginTx(ctx, nil)
	if err != nil {
		return ti, fmt.Errorf("could not begin tx: %v", err)
	}

	defer tx.Rollback()

	query := "INSERT INTO posts (user_id, content, spoiler_of, nsfw) VALUES ($1, $2, $3, $4) " +
		"RETURNING id, created_at"
	if err = tx.QueryRowContext(ctx, query, uid, content, spoilerOf, nsfw).
		Scan(&ti.Post.ID, &ti.Post.CreatedAt); err != nil {
		return ti, fmt.Errorf("could not insert post: %v", err)
	}

	ti.Post.UserID = uid
	ti.Post.Content = content
	ti.Post.SpoilerOf = spoilerOf
	ti.Post.NSFW = nsfw
	ti.Post.Mine = true

	query = "INSERT INTO timeline (user_id, post_id) VALUES ($1, $2) RETURNING id"
	if err = tx.QueryRowContext(ctx, query, uid, ti.Post.ID).Scan(&ti.ID); err != nil {
		return ti, fmt.Errorf("could not insert timeline item: %v", err)
	}

	ti.UserID = uid
	ti.PostID = ti.Post.ID

	if err = tx.Commit(); err != nil {
		return ti, fmt.Errorf("could not commit to create post: %v", err)
	}

	go func(p Post) {
		u, err := s.userByID(context.Background(), p.UserID)
		if err != nil {
			log.Printf("could not get post user: %v\n", err)
			return
		}

		p.User = &u
		p.Mine = false

		tt, err := s.fanoutPost(p)
		if err != nil {
			log.Printf("could not fanout post: %v\n", err)
			return
		}

		for _, ti = range tt {
			log.Println(litter.Sdump(ti))
			// TODO: broadcast timeline items.
		}
	}(ti.Post)

	// TODO: notify each mentioned user in posts.

	return ti, nil
}

func (s *Service) fanoutPost(p Post) ([]TimelineItem, error) {
	query := "INSERT INTO timeline (user_id, post_id) " +
		"SELECT follower_id, $1 FROM follows WHERE followee_id = $2 " +
		"RETURNING id, user_id"
	rows, err := s.db.Query(query, p.ID, p.UserID)
	if err != nil {
		return nil, fmt.Errorf("could not insert timeline: %v", err)
	}

	defer rows.Close()

	tt := []TimelineItem{}
	for rows.Next() {
		var ti TimelineItem
		if err = rows.Scan(&ti.ID, &ti.UserID); err != nil {
			return nil, fmt.Errorf("could not scan timeline item: %v", err)
		}

		ti.PostID = p.ID
		ti.Post = p
		tt = append(tt, ti)
	}

	if err = rows.Err(); err != nil {
		return nil, fmt.Errorf("could not iterate timeline rows: %v", err)
	}

	return tt, nil
}
<|endoftext|>